// Copyright 2020 南宁马拉松10公里组四大天王.
// License GPL (基于本代码修改需按相同协议开源)
// Project: 天王麻将 | SkyKing Mahjong
// Module: Dessert Bot(点心AI)
// Version: 0.0.1(Michelangelo)
// Author: 天王用叉 & 天王用棍
// Email for Game Issue: 天王用叉(19366448@qq.com)
// Email for Code Issue: 天王用棍(yexiang841@163.com)
// Outline:
/*
  Bot
      < DessertBot |- EyeOfBot
                   |            < EyeOfBotJson
                   |            < EyeOfBotSimple
                   |
                   |- MouthOfBot
                   |            < MouthOfBotJson
                   |            < MouthOfBotSimple
                   |
                   |- BrainOfBot  |
                   |              |- requests_ / responses_
                   |              |- last_data_ / globaldata_
                   |              |- Event(last/now) |
                   |              |                  |- code / player_id / action
                   |              |                  |- tDRAW / tPLAY / tSEQUENCE_mid / tBUGANG
                   |              |- MemoryPlus
                   |              |- MemoryCore1 (if possible)
                   |              |- MemoryCore2 (if possible)
                   |              |- MemoryCore3 (if possible)
                   |              |- ...
                   |
                   |-- Read / Sit / Deal / Rebuild / Judge / Think / Response

           / < MyTileCore
  TileCore        +       < MyTilePlus
           \ < TilePlus

           / < NotMyTileCore
  TileCore        +          < NotMyTilePlus
           \ < TilePlus

  TileCore |
           |- my_player_id_ / hand_count_ / wall_count_ / discard / claim_sequence / claim_triplet / claim_exposed_kong

  MyTileCore |
             |- hand / claim_concealed_kong

  TilePlus |
           |- pos_xxx / bit_xxx

  Memory |
         |- my_player_id_
         |- tile_arised

    < MemoryCore |
                 |- MyTilesCore + NotMyTilesCore x 3

    < MemoryPlus |
                 |- MyTilesPlus + NotMyTilesPlus x 3

  Logger
         |- PenOfLog
         |           < PenOfLogOnline
         |           < PenOfLogLocal
         |
         |-- Log / Debug / DebugInit / DebugSkip / DebugMark / DebugMarkCost / DebugStep

  Watch
         |-- Step/Mark
         |-- TimeFromInit / TimeFromStep / TimeFromMark

  main:
      -> bot.Read -> bot.Sit -> bot.Deal -> bot.Rebuild -> bot.Judge -> bot.Think -> bot.Response

*/

#include <iostream>       // 标准输入输出用到
#include <fstream>        // 文件输入输出用到
#include <string>         // 字符串用到
#include <sstream>        // 构造字符串用到
#include <vector>         // 数据结构用到
#include <unordered_map>  // 建立action字符串映射用到
#include <chrono>         // 计时器用到
#include "jsoncpp/json.h" // 本地和线上使用相同的json包

// 怎么都得先定义一个命名空间,不要给别人的代码造成麻烦
namespace skyking
{

    // 根据Google C++ 代码规范,禁用using namespace污染命名空间,用using
    using std::bitset;
    using std::cin;
    using std::cout;
    using std::endl;
    using std::ifstream;
    using std::ios;
    using std::istream;
    using std::istringstream;
    using std::ofstream;
    using std::ostringstream;
    using std::setfill;
    using std::setiosflags;
    using std::setw;
    using std::string;
    using std::to_string;
    using std::unordered_map;
    using std::vector;
    using std::chrono::duration_cast;
    using std::chrono::microseconds;
    using std::chrono::steady_clock;

    // 程序开始时间(static用作文件隔离)
    static steady_clock::time_point bot_init_time = steady_clock::now();

    /*
       宏定义部分
    */

// #define _BOTZONE_ONLINE
// 上面这个宏由botzone在线编译时传入
// 代码中可以根据是否有这个宏决定本地测试行为
// 当需要模拟线上运行时可以打开

// 1表示长时运行模式
// 0表示回合模式
// TODO(天王用棍):不知道平台如何定义
#define _DAEMON_BASED 0

// 1表示Json交互
// 0表示简单交互
// TODO(天王用棍):不知道平台如何定义
#define _JSON_IO 1

// 1表示会在响应中输出本回合生成的data字段
// 0表示不会
#define _RESPONSE_WITH_DATA 1

// 1表示在响应中输出的data字段是以bitset格式存储
// 0表示以vector格式存储
// 这个字段只在本地测试(if _RESPONSE_WITH_DATA)时生效,否则会自动忽略
#define _RESPONSE_WITH_DATA_AS_BIT 0

// 1表示打印所有日志
// 0表示只打印写入debug的日志
// 这个字段只在本地测试(ifndef _BOTZONE_ONLINE)时生效,否则会自动忽略
#define _PRINT_ALL_LOG 0

// 1表示如果输入不含data字段,就从本地文件读取data字段
// 0表示即使输入不含data字段,也不会从本地文件读取data字段
// 这个字段只在本地测试时(ifndef _BOTZONE_ONLINE)生效
#define _READ_DATA_LAST_LOCAL 0

// 1表示会输出本回合生成的data字段到文件
// 0表示不会
// 这个字段只在本地测试时(ifndef _BOTZONE_ONLINE)生效
#define _WRITE_DATA_NOW_LOCAL 1

// 变量名变字符数组
#define name_to_str(val) Util::chars_to_string(#val)

    /*
       枚举声明部分
    */

    // 打牌动作集
    enum Action
    {
        NA = (unsigned short)0,      // 为空,无意义
        SIT = (unsigned short)1,     // 落座
        DEAL = (unsigned short)2,    // 发牌
        PASS = (unsigned short)3,    // PASS
        IDRAW = (unsigned short)4,   // 自己摸牌
        DRAW = (unsigned short)5,    // 其它玩家摸牌
        PLAY = (unsigned short)6,    // 出牌
        PENG = (unsigned short)7,    // 碰
        CHI = (unsigned short)8,     // 吃
        GANG = (unsigned short)9,    // 杠(根据上回合的Action判断本回合是明杠还是暗杠)
        BUGANG = (unsigned short)10, // 补杠
        HU = (unsigned short)11,     // 胡

    }; // end Action

    /**
         * @brief 所有牌的值，不包括花牌
         */
    enum TileValue
    {
        TILE_VALUE_1W = 0x11,
        TILE_VALUE_2W,
        TILE_VALUE_3W,
        TILE_VALUE_4W,
        TILE_VALUE_5W,
        TILE_VALUE_6W,
        TILE_VALUE_7W,
        TILE_VALUE_8W,
        TILE_VALUE_9W,
        TILE_VALUE_1T = 0x21,
        TILE_VALUE_2T,
        TILE_VALUE_3T,
        TILE_VALUE_4T,
        TILE_VALUE_5T,
        TILE_VALUE_6T,
        TILE_VALUE_7T,
        TILE_VALUE_8T,
        TILE_VALUE_9T,
        TILE_VALUE_1B = 0x31,
        TILE_VALUE_2B,
        TILE_VALUE_3B,
        TILE_VALUE_4B,
        TILE_VALUE_5B,
        TILE_VALUE_6B,
        TILE_VALUE_7B,
        TILE_VALUE_8B,
        TILE_VALUE_9B,
        TILE_VALUE_E = 0x41,
        TILE_VALUE_S,
        TILE_VALUE_W,
        TILE_VALUE_N,
        TILE_VALUE_C,
        TILE_VALUE_F,
        TILE_VALUE_P,
        TILE_VALUE_TOP
    }; // end TileValue

    // 花色
    enum TileSuit
    {
        TILE_SUIT_NA = (unsigned char)0, // 不合法花色
        TILE_SUIT_W = (unsigned char)1,  // 万
        TILE_SUIT_T = (unsigned char)2,  // 条
        TILE_SUIT_B = (unsigned char)3,  // 饼
        TILE_SUIT_Z = (unsigned char)4,  // 字

    }; // end TileSuit

    /*
       结构声明部分
    */

    // 不习惯用全局函数的话,可以按照工程习惯兜起来
    struct Util
    {
        // 字符数组变字符串
        static string chars_to_string(const char *val) { return val; };

        // 字符串按字符分割
        static void split(const string &s, vector<string> &tokens, char delim = ' ')
        {
            tokens.clear();
            auto string_find_first_not = [s, delim](size_t pos = 0) -> size_t {
                for (size_t i = pos; i < s.size(); i++)
                {
                    if (s[i] != delim)
                        return i;
                }
                return string::npos;
            };
            size_t lastPos = string_find_first_not(0);
            size_t pos = s.find(delim, lastPos);
            while (lastPos != string::npos)
            {
                tokens.emplace_back(s.substr(lastPos, pos - lastPos));
                lastPos = string_find_first_not(pos);
                pos = s.find(delim, lastPos);
            }
        };

        // vector<unsigned char>按分割符组合成字符串
        static string merge_unsigned_char(vector<unsigned char> &v, char delim = ' ')
        {
            ostringstream oss;
            if (v.size() == 0)
            {
                oss << delim;
            }
            else
            {
                for_each(v.begin(), v.end(), [&oss, delim](unsigned char value) { oss << to_string((unsigned short)value) << delim; });
            }
            return oss.str();
        }

        // vector<unsigned short>按分割符组合成字符串
        static string merge_unsigned_short(vector<unsigned short> &v, char delim = ' ')
        {
            ostringstream oss;
            if (v.size() == 0)
            {
                oss << delim;
            }
            else
            {
                for_each(v.begin(), v.end(), [&oss, delim](unsigned short value) { oss << to_string(value) << delim; });
            }
            return oss.str();
        }
    }; // end Util

    // 全局配置
    struct Config
    {
        // 几个本地测试文件名字
        const string kFileRequestJson = "request_local.json";
        const string kFileRequestSimple = "request_local.simple";
        const string kFileDataLastLocal = "data_last_local.json";
        const string kFileDataNowLocal = "data_now_local.json";
    } bot_config; // end Config

    // 牌结构(为了使用算番器,使用一样的数据格式)
    struct Tile
    {
        /**
         * @brief 牌\n
         * 内存结构：
         * - 0-3 4bit 牌的点数
         * - 4-7 4bit 牌的花色
         * 合法的牌为：
         * - 0x11 - 0x19 万子（CHARACTERS）
         * - 0x21 - 0x29 条子（BAMBOO）
         * - 0x31 - 0x39 饼子（DOTS）
         * - 0x41 - 0x47 字牌（HONORS）
         * - 0x51 - 0x58 花牌（FLOWER）
         */

        // 平台约定的牌名数
        static const unsigned short kTileNameCount = 34;
        // 平台约定的牌墙数
        static const unsigned short kWallCount = kTileNameCount;
        // 平台约定的总牌数
        static const unsigned short kTileCount = 4 * kTileNameCount;

        /**
         * @brief 所有合法的牌，不包括花牌
         */
        const unsigned char kTileValueList[kTileNameCount] = {
            TILE_VALUE_1W, TILE_VALUE_2W, TILE_VALUE_3W, TILE_VALUE_4W, TILE_VALUE_5W, TILE_VALUE_6W, TILE_VALUE_7W, TILE_VALUE_8W, TILE_VALUE_9W,
            TILE_VALUE_1T, TILE_VALUE_2T, TILE_VALUE_3T, TILE_VALUE_4T, TILE_VALUE_5T, TILE_VALUE_6T, TILE_VALUE_7T, TILE_VALUE_8T, TILE_VALUE_9T,
            TILE_VALUE_1B, TILE_VALUE_2B, TILE_VALUE_3B, TILE_VALUE_4B, TILE_VALUE_5B, TILE_VALUE_6B, TILE_VALUE_7B, TILE_VALUE_8B, TILE_VALUE_9B,
            TILE_VALUE_E, TILE_VALUE_S, TILE_VALUE_W, TILE_VALUE_N, TILE_VALUE_C, TILE_VALUE_F, TILE_VALUE_P};

        // 全部牌名(不含花牌)
        const string kTileNameList[kTileNameCount] = {
            "W1", "W2", "W3", "W4", "W5", "W6", "W7", "W8", "W9", //  0 ~  8 / "W"+(i+1)
            "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", //  9 ~ 17 / "T"+(i-9)
            "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", // 18 ~ 26 / "B"+(i-18)
            "F1", "F2", "F3", "F4",                               // 27 ~ 30 / "F"+(i-27)
            "J1", "J2", "J3",                                     // 31 ~ 33 / "J"+(i-30)
        };
        // 全部牌名到牌值的映射
        unordered_map<string, unsigned char> kTileName2Value;
        // 全部牌值到牌名的映射
        unordered_map<unsigned char, string> kTileValue2Name;
        // 全部牌值到牌位的映射
        unordered_map<unsigned char, unsigned short> kTileValue2Pos;
        // 全局牌名出现次数:每一种牌出现过多少张
        // 此处初始化一个,以后就可以通过拷贝赋值省去重新构建时插入的时间损耗
        unordered_map<unsigned char, unsigned short> kTileArised;
        Tile()
        {
            for (int i = 0; i <= kTileNameCount; ++i)
            {
                kTileName2Value[kTileNameList[i]] = kTileValueList[i];
                kTileValue2Name[kTileValueList[i]] = kTileNameList[i];
                kTileValue2Pos[kTileValueList[i]] = i;
                kTileArised[kTileValueList[i]] = 0;
            }
            // 为了处理初始化值和未定义值,加一个牌
            kTileName2Value["NA"] = 0x00;
            kTileValue2Name[0x00] = "NA";
        }

        /**
         * @brief 生成一张牌
         * @param [in] suit 花色
         * @param [in] rank 点数
         * @return unsinged char 牌
         */
        static inline unsigned char make_tile(unsigned char suit, unsigned char rank)
        {
            return (((suit & 0xF) << 4) | (rank & 0xF));
        }

        /**
         * @brief 获取牌的花色
         * @param [in] tile 牌
         * @return unsigned char 花色
         */
        static inline unsigned char tile_get_suit(unsigned char tile)
        {
            return ((tile >> 4) & 0xF);
        }

        /**
         * @brief 判断是否为花牌
         * @param [in] tile 牌
         * @return bool
         */
        static inline bool is_flower(unsigned char tile)
        {
            return ((tile >> 4) & 0xF) == 5;
        }

        /**
         * @brief 获取牌的点数
         * @param [in] tile 牌
         * @return unsigned char 点数
         */
        static inline unsigned char tile_get_rank(unsigned char tile)
        {
            return (tile & 0xF);
        }

        // 将玩家的动作名映射为短数字
        // 用于优化对字符串判断操作的效率,利用unordered_map的hash算法
        const unordered_map<string, unsigned char> kActionMap = {
            {name_to_str(NA), NA},
            {name_to_str(SIT), SIT},
            {name_to_str(DEAL), DEAL},
            {name_to_str(PASS), PASS},
            {name_to_str(IDRAW), IDRAW},
            {name_to_str(DRAW), DRAW},
            {name_to_str(PLAY), PLAY},
            {name_to_str(PENG), PENG},
            {name_to_str(CHI), CHI},
            {name_to_str(GANG), GANG},
            {name_to_str(BUGANG), BUGANG},
            {name_to_str(HU), HU},
        };
        const unordered_map<unsigned char, string> kActionRMap = {
            {NA, name_to_str(NA)},
            {SIT, name_to_str(SIT)},
            {DEAL, name_to_str(DEAL)},
            {PASS, name_to_str(PASS)},
            {IDRAW, name_to_str(IDRAW)},
            {DRAW, name_to_str(DRAW)},
            {PLAY, name_to_str(PLAY)},
            {PENG, name_to_str(PENG)},
            {CHI, name_to_str(CHI)},
            {GANG, name_to_str(GANG)},
            {BUGANG, name_to_str(BUGANG)},
            {HU, name_to_str(HU)},
        };
    } bot_tile; // end Config

    // 事件结构体,特指第2回合(从0数起)之后的全局事件(code==2/3的request)
    struct Event
    {
        unsigned short turn;         // 当前事件发生在哪个回合
        unsigned short code;         // 事件类型,标准输入的第一位(0/1/2/3),0表示落座,1表示发牌,2表示摸牌,3表示全局信息
        unsigned short player_id;    // 事件来源(仅当事件类型为3时),值为0/1/2/3,有可能等于自己的id(表示动作确认)
        unsigned short action;       // 动作类型(仅当事件类型为3时),值为Action枚举(DRAW/PLAY/PENG/CHI/GANG/BUGANG)
        unsigned char tDRAW;         // 摸到的牌(仅当事件类型为2时)
        unsigned char tPLAY;         // 玩家打出的牌(仅当事件类型为3,并且动作为"PLAY"时)
        unsigned char tSEQUENCE_mid; // 玩家吃的顺子中,中间那张牌(仅当事件类型为3,并且动作为"CHI"时)
        unsigned char tBUGANG;       // 玩家补杠的牌(仅当事件类型为3,并且动作为"BUGANG"时)
        // 序列化为字符串
        string data_to_string()
        {
            return to_string(turn) + "," + to_string(code) + "," + to_string(player_id) + "," + to_string(action) + "," + to_string(tDRAW) + "," + to_string(tPLAY) + "," + to_string(tSEQUENCE_mid) + "," + to_string(tBUGANG) + "," + "|";
        }
        // 从字符串载入数据,注意顺序要与data_to_string()一致
        void string_to_data(string &rebuild_data)
        {
            vector<string> string_data;
            Util::split(rebuild_data, string_data, ',');
            turn = std::stoi(string_data[0]);
            code = std::stoi(string_data[1]);
            player_id = std::stoi(string_data[2]);
            action = std::stoi(string_data[3]);
            tDRAW = std::stoi(string_data[4]);
            tPLAY = std::stoi(string_data[5]);
            tSEQUENCE_mid = std::stoi(string_data[6]);
            tBUGANG = std::stoi(string_data[7]);
        }
    } Event_Default = {(unsigned short)0, (unsigned short)9, (unsigned short)9, (unsigned short)0, 0x00, 0x00, 0x00, 0x00}; // 初始化一个默认的用来快速复制

    /*
       类声明部分
    */

    class PenOfLogger;       // 基类:日志的输出笔
    class PenOfLoggerOnline; //   日志的输出笔(记录到debug字段)
    class PenOfLoggerLocal;  //   日志的输出笔(本地打印)
    class Logger;            // 日志
    class Watch;             // 计时器
    class EyeOfBot;          // 基类:机器人的眼睛
    class EyeOfBotJson;      //   机器人的眼睛(Json交互)
    class EyeOfBotSimple;    //   机器人的眼睛(简单交互)
    class MouthOfBot;        // 基类:机器人的嘴
    class MouthOfBotJson;    //   机器人的嘴(Json交互)
    class MouthOfBotSimple;  //   机器人的嘴(简单交互)
    class TilesCore;         // 基类:牌面基类(精简数据集)
    class MyTilesCore;       //   自己的牌面(精简数据集)
    class NotMyTilesCore;    //   其它玩家的牌面(精简数据集)
    class TilesPlus;         // 基类:扩展牌面
    class MyTilesPlus;       //   自己的牌面(扩展数据集)
    class NotMyTilesPlus;    //   其它玩家的牌面(扩展数据集)
    class Memory;            // 基类:机器人的存储
    class MemoryCore;        //   机器人的存储(精简数据集,用来进行模拟计算)
    class MemoryPlus;        //   机器人的存储(完整数据集,用来存储真实数据)
    class BrainOfBot;        // 机器人的大脑
    class Bot;               // 机器人抽象基类
    class DessertBot;        // 机器人的第一版实现:点心AI

    /*
       类定义部分
       逻辑简单的当场实现
    */

    // 日志的输出笔(抽象基类)
    class PenOfLogger
    {
    protected:
        // 官方标准字段debug,从LogOfBot中引用过来
        ostringstream &debug_;

    public:
        explicit PenOfLogger(ostringstream &debug) : debug_(debug){};
        virtual ~PenOfLogger(){};
        // 单条日志输出
        virtual void output(const ostringstream &log) = 0;
    }; // end PenOfLogger

    // 日志的输出笔(记录到debug字段)
    class PenOfLoggerOnline : public PenOfLogger
    {
    public:
        explicit PenOfLoggerOnline(ostringstream &debug) : PenOfLogger(debug){};
        ~PenOfLoggerOnline(){};
        // 单条日志写入debug日志集
        void output(const ostringstream &log) { debug_ << log.str(); }
    }; // end PenOfLoggerOnline

    // 日志的输出笔(本地打印)
    class PenOfLoggerLocal : public PenOfLogger
    {
    public:
        explicit PenOfLoggerLocal(ostringstream &debug) : PenOfLogger(debug){};
        ~PenOfLoggerLocal(){};
        // 单条日志本地打印
        void output(const ostringstream &log)
        {
            debug_ << log.str();
            cout << log.str();
        }
    }; // end PenOfLoggerLocal

    // 日志
    class Logger
    {
    protected:
        // 日志写入器
        PenOfLogger *pen_;
        // 官方标准字段debug,把日志打入此debug,如果是线上运行,最后输出到json
        ostringstream debug_;

    public:
        Logger()
        {
#ifdef _BOTZONE_ONLINE // 如果是线上编译运行,为日志新建写入debug字段的笔
            pen_ = new PenOfLoggerOnline(debug_);
#else // 如果是本地测试,为日志新建本地打印的笔
            pen_ = new PenOfLoggerLocal(debug_);
#endif
            Log("logger_created");
        }
        ~Logger()
        {
            Log("logger_deleted");
            delete pen_; // 逢new必delete
        }
        string debug() const { return debug_.str(); } // 读取debug中记录的所有日志
        // 静态函数:打印log(不计入debug字段)
        static void Log(const string &log)
        {
#ifndef _BOTZONE_ONLINE
            //只有当本地测试时才会调用到这里
#if _PRINT_ALL_LOG
            //只有当打印开关打开时才打印
            cout << log << endl;
#endif
#endif
        }
        void Debug(const string &log);                        // 记录log(计入debug字段)
        void DebugInit(const microseconds time_interval);     // 记录初始化耗时
        void DebugSkip(const string &log);                    // 记录跳过的步骤
        void DebugMark();                                     // 记录MARK开始
        void DebugMarkCost(const microseconds time_interval); // 记录MARK计时
        // 记录log和时间(Bot单步骤专用)
        void DebugStep(const string &log, const microseconds time_interval);
    }; // end Logger

    // 全局日志对象(伪单例)
    static Logger bot_logger;

    // 计时器
    class Watch
    {
    protected:
        // 存储bot最开始的时间,用于计算总时长
        const steady_clock::time_point bot_init_time_;
        // 上个步骤开始时间,用于计算到步骤结束的运算时间
        mutable steady_clock::time_point step_start_time_;
        // 上一次标记的时间,用于计算到下一个节点的运算时间
        mutable steady_clock::time_point last_mark_time_;

    public:
        explicit Watch(const steady_clock::time_point &bot_init_time)
            : bot_init_time_(bot_init_time),
              step_start_time_(bot_init_time),
              last_mark_time_(bot_init_time)
        {
            Logger::Log("watch_created");
        }
        ~Watch() { Logger::Log("watch_deleted"); }
        // 设定标记时间为此刻
        void Mark();
        // 记录上次MARK时间到此刻的耗时
        void MarkCost();
        // 设定步骤开始时间为此刻
        inline void Step() { step_start_time_ = steady_clock::now(); }
        // 计算开始时间到此刻的耗时
        inline microseconds TimeFromInit() { return duration_cast<microseconds>(steady_clock::now() - bot_init_time_); }
        // 计算上次MARK时间到此刻的耗时
        inline microseconds TimeFromMark() { return duration_cast<microseconds>(steady_clock::now() - last_mark_time_); }
        // 计算上次Step时间到此刻的耗时
        inline microseconds TimeFromStep() { return duration_cast<microseconds>(steady_clock::now() - step_start_time_); }
    }; // end Watch

    // 全局计时器对象(伪单例)
    static Watch watch(bot_init_time);

    // 机器人的眼睛:标准输入
    class EyeOfBot
    {
    protected:
        BrainOfBot *&brain_;

    public:
        explicit EyeOfBot(BrainOfBot *&brain) : brain_(brain) {}
        virtual ~EyeOfBot() {}
        // 读取输入(回合模式)
        void ReadInput()
        {
#ifdef _BOTZONE_ONLINE // 如果是线上编译运行,从标准输入读取request
            ReadStdInput();
#else // 如果是本地测试,从文件读取request
            ReadFileInput();
#endif
        }
        // 如果是本地测试,从文件读取上回合data(回合模式)
        Json::Value ReadLastDataIfLocal();
        virtual void ReadStdInput() = 0;
        // 基类纯虚函数:读取文件输入(回合模式)
        virtual void ReadFileInput() = 0;
    }; // end EyeOfBot

    // 机器人的眼睛(Json交互)
    class EyeOfBotJson : public EyeOfBot
    {
    protected:
        //Json解析器,长时运行模式下不用构造那么多次,可以复用
        Json::Reader json_reader_;
        // 从输入生成requests和responses和data(JSON交互/回合模式)
        void ReadRequestAndResponseAndData(istream &in);

    public:
        explicit EyeOfBotJson(BrainOfBot *&brain) : EyeOfBot(brain) { Logger::Log("eye_(json)_created"); }
        ~EyeOfBotJson() { Logger::Log("eye_(json)_deleted"); }
        // 重写:读取标准输入(JSON交互/回合模式)
        void ReadStdInput();
        // 重写:读取文件输入(JSON交互/回合模式)
        void ReadFileInput();
    }; // end EyeOfBotJson

    // 机器人的眼睛(简单交互)
    class EyeOfBotSimple : public EyeOfBot
    {
    protected:
        // 从输入生成requests和responses和data(简单交互/回合模式)
        void ReadRequestAndResponseAndData(istream &in);

    public:
        explicit EyeOfBotSimple(BrainOfBot *&brain) : EyeOfBot(brain) { Logger::Log("eye_(simple)_created"); }
        ~EyeOfBotSimple() { Logger::Log("eye_(simple)_deleted"); }
        // 重写:读取标准输入(简单交互/回合模式)
        void ReadStdInput();
        // 重写:读取文件输入(简单交互/回合模式)
        void ReadFileInput();
    }; // end EyeOfBotSimple

    // 机器人的嘴:标准输出
    class MouthOfBot
    {
    protected:
    public:
        MouthOfBot() {}
        virtual ~MouthOfBot() {}
        // 基类纯虚函数:标准输出
        virtual void PrintStdOutput(const string &response) = 0;
        // 将标准data字段写入文件(仅测试用)
        void WriteDataNowIfLocal(const string &data);
    }; // end MouthOfBot

    // 机器人的嘴(Json交互)
    class MouthOfBotJson : public MouthOfBot
    {
    protected:
    public:
        MouthOfBotJson() { Logger::Log("mouth_(json)_created"); }
        ~MouthOfBotJson() { Logger::Log("mouth_(json)_deleted"); }
        // 重写:标准输出(Json交互)
        void PrintStdOutput(const string &response);
    }; // end MouthOfBotJson

    // 机器人的嘴(简单交互)
    class MouthOfBotSimple : public MouthOfBot
    {
    protected:
    public:
        MouthOfBotSimple() { Logger::Log("mouth_(simple)_created"); }
        ~MouthOfBotSimple() { Logger::Log("mouth_(simple)_deleted"); }
        // 重写:标准输出(简单交互)
        void PrintStdOutput(const string &response);
    }; // end MouthOfBotSimple

    // 牌面基类(精简数据集)
    // 包括:牌墙(数)/弃牌/鸣牌
    // 其中鸣牌又包括:顺牌/刻牌/明杠
    class TilesCore
    {
    protected:
        const unsigned short my_player_id_;               // 当前牌面属于几号玩家
        mutable unsigned short wall_count_;               // 牌墙数
        mutable vector<unsigned char> discard;            // 弃牌 // TODO:手牌应该新增排序的数据结构
        mutable vector<unsigned char> claim_sequence;     // 鸣牌中的顺牌:长度是3的倍数,一次存3张
        mutable vector<unsigned char> claim_triplet;      // 鸣牌中的刻牌:长度是3的倍数,一次存3张
        mutable vector<unsigned char> claim_exposed_kong; // 鸣牌中的明杠:长度是4的倍数,一次存4张
        friend class Memory;

    public:
        explicit TilesCore(unsigned short player_id)
            : my_player_id_(player_id),     // 当前牌面属于几号玩家
              wall_count_(Tile::kWallCount) // 全局配置的牌墙数
        {
            // 为了节省空间,对各牌面数量做一个限制
            discard.reserve(21);            // 弃牌最多21张(牌山最多34张,而因为手牌+鸣牌至少会有13张,相减就是弃牌)
            claim_sequence.reserve(12);     // 鸣牌中的顺牌最多4副12张
            claim_triplet.reserve(12);      // 鸣牌中的刻牌最多4副12张
            claim_exposed_kong.reserve(16); // 鸣牌中的明杠最多4副16张
        }
        virtual ~TilesCore() {}
        // 设置牌墙(数)
        inline void set_wall_count(unsigned short wall_count) const { wall_count_ = wall_count; }
        // 减少牌墙(数)
        inline unsigned short dec_wall_count(unsigned short count) const { return wall_count_ -= count; }
        // 新增弃牌(牌)
        void inc_discard(unsigned char tile) { discard.push_back(tile); }
        // 删去弃牌(牌)
        // 原因是其它玩家吃/碰/杠
        // 从后面往前删
        unsigned short dec_discard(unsigned char tile)
        {
            vector<unsigned char>::iterator iter = (++find(discard.rbegin(), discard.rend(), tile)).base();
            discard.erase(iter);           // 从序列中删去
            return iter - discard.begin(); // 取位置下标
        }
        // 新增顺牌(牌)
        void inc_claim_sequence(unsigned char tile) { claim_sequence.push_back(tile); }
        // 新增刻牌(牌)
        void inc_claim_triplet(unsigned char tile) { claim_triplet.push_back(tile); }
        // 删去刻牌(牌)
        // 仅用于补杠
        unsigned short dec_claim_triplet(unsigned char tile)
        {
            vector<unsigned char>::iterator iter = find(claim_triplet.begin(), claim_triplet.end(), tile);
            claim_triplet.erase(iter);           // 从序列中删去
            return iter - claim_triplet.begin(); // 取位置下标
        }
        // 新增明杠(牌)
        void inc_claim_exposed_kong(unsigned char tile) { claim_exposed_kong.push_back(tile); }
        // 寻找某个序列v中最后一张tile所在的全局位置
        static unsigned short FindLastPos(vector<unsigned char> &v, vector<unsigned short> &v_pos, unsigned char tile)
        {
            vector<unsigned char>::iterator iter = (++find(v.rbegin(), v.rend(), tile)).base();
            return v_pos[iter - v.begin()];
        }
        // 寻找某个序列v中第一张tile所在的全局位置
        static unsigned short FindFirstPos(vector<unsigned char> &v, vector<unsigned short> &v_pos, unsigned char tile)
        {
            vector<unsigned char>::iterator iter = find(v.begin(), v.end(), tile);
            return v_pos[iter - v.begin()];
        }
    }; // end TilesCore

    // 自己的牌面(精简数据集)
    // 在牌面基类基础上新增:手牌/暗杠
    class MyTilesCore : virtual public TilesCore // public方式继承,相当于加上TilesCore的所有数据
    {
    protected:
        mutable vector<unsigned char> hand;                 // 手牌 // TODO:手牌应该新增排序的数据结构
        mutable vector<unsigned char> claim_concealed_kong; // 暗杠:长度是4的倍数,一次存4张
        friend class Memory;

    public:
        MyTilesCore(unsigned short my_player_id) : TilesCore(my_player_id)
        {
            // 为了节省空间,对各牌面数量做一个限制
            hand.reserve(14);                 // 手牌最多14张(包括刚抓上来那张)
            claim_concealed_kong.reserve(16); // 鸣牌中的暗杠最多4副16张
                                              // PS:手牌+鸣牌最多18张(包括刚抓上来那张)
            Logger::Log("tiles_core_" + to_string(my_player_id) + "(my)_created");
        }
        ~MyTilesCore() { Logger::Log("tiles_core_" + to_string(my_player_id_) + "(my)_deleted"); }
        // 新增手牌(牌)
        void inc_hand(unsigned char tile) { hand.push_back(tile); }
        // 删去手牌(牌)
        // 注意这里未必是打出,也有可能是鸣牌
        unsigned short dec_hand(unsigned char tile)
        {
            vector<unsigned char>::iterator iter = find(hand.begin(), hand.end(), tile);
            hand.erase(iter);           // 从序列中删去
            return iter - hand.begin(); // 取位置下标
        }
        // 新增暗杠(牌)
        void inc_claim_concealed_kong(unsigned char tile) { claim_concealed_kong.push_back(tile); }
    }; // end MyTilesCore

    // 其它玩家的牌面(精简数据集)
    // 在牌面基类基础上新增:手牌(数)/暗杠(数)
    class NotMyTilesCore : virtual public TilesCore // public方式继承,相当于加上TilesCore的所有数据
    {
    protected:
        mutable unsigned short hand_count_;                 // 手牌(数)
        mutable unsigned short claim_concealed_kong_count_; // 暗杠(数)
        friend class Memory;
        friend class MemoryPlus;

    public:
        NotMyTilesCore(unsigned short player_id) : TilesCore(player_id), hand_count_(0), claim_concealed_kong_count_(0)
        {
            Logger::Log("tiles_core_" + to_string(player_id) + "_created");
        }
        ~NotMyTilesCore() { Logger::Log("tiles_core_" + to_string(my_player_id_) + "_deleted"); }
        // 设置手牌(数)
        inline void set_hand_count(unsigned short hand_count) const { hand_count_ = hand_count; }
        // 增加手牌数
        inline unsigned short inc_hand_count(unsigned short count) { return hand_count_ += count; }
        // 减少手牌数
        inline unsigned short dec_hand_count(unsigned short count) { return hand_count_ -= count; }
        // 设置暗杠(数)
        inline void set_claim_concealed_kong_count(unsigned short claim_concealed_kong_count) const { claim_concealed_kong_count_ = claim_concealed_kong_count; }
        // 增加暗杠数
        inline unsigned short inc_claim_concealed_kong_count(unsigned short count) { return claim_concealed_kong_count_ += count; }
    }; // end NotMyTilesCore

    // 扩展牌面
    // 每个可见牌区对应一个bit_XXX,比如弃牌区有bit_discard
    // 每个bit_XXX都是一个全局排序的比特位(0/1),1表示这张牌存在于该可见牌区
    // bit_XXX中每个牌名有四张牌，按照出现次序从左到右归位
    // 计算公式: 位置 = 4 x 牌名(牌名也有一个数字序列) + 全局出现的次数[牌名]
    // pos_XXX表示XXX中每张牌在全局排序中的位置
    class TilesPlus : virtual protected TilesCore // protected方式继承,将TilesCore的所有函数继承但转为对外不可见,目的是为了重载(加上位置信息)
    {
    protected:
        mutable bitset<Tile::kTileCount + 4> bit_discard;
        mutable bitset<Tile::kTileCount + 4> bit_claim_sequence;
        mutable bitset<Tile::kTileCount + 4> bit_claim_triplet;
        mutable bitset<Tile::kTileCount + 4> bit_claim_exposed_kong;
        mutable vector<unsigned short> pos_discard;
        mutable vector<unsigned short> pos_claim_sequence;
        mutable vector<unsigned short> pos_claim_triplet;
        mutable vector<unsigned short> pos_claim_exposed_kong;
        friend class MemoryPlus;

    public:
        explicit TilesPlus(unsigned short player_id)
            : TilesCore(player_id) // 当前牌面属于几号玩家
        {
            // 为了节省空间,对各牌面数量做一个限制
            pos_discard.reserve(21);
            pos_claim_sequence.reserve(12);
            pos_claim_triplet.reserve(12);
            pos_claim_exposed_kong.reserve(16);
        }
        virtual ~TilesPlus() {}
        // 新增弃牌(牌)
        void inc_discard(unsigned char tile, unsigned short pos)
        {
            TilesCore::inc_discard(tile); // 放入序列
            bit_discard.set(pos);         // 比特置位
            pos_discard.push_back(pos);   // 位置放入序列
        }
        // 删去弃牌(牌)
        // 因为其它玩家吃/碰/杠
        // 从后面往前删
        unsigned short dec_discard(unsigned char tile)
        {
            unsigned short index = TilesCore::dec_discard(tile); // 放入序列
            bit_discard.reset(pos_discard[index]);               // 比特置空位
            pos_discard.erase(pos_discard.begin() + index);      // 位置序列中删去
            return index;
        }
        // 新增顺牌(牌)
        void inc_claim_sequence(unsigned char tile, unsigned short pos)
        {
            TilesCore::inc_claim_sequence(tile); // 放入序列
            bit_claim_sequence.set(pos);         // 比特置位
            pos_claim_sequence.push_back(pos);   // 位置放入序列
        }
        // 新增刻牌(牌)
        void inc_claim_triplet(unsigned char tile, unsigned short pos)
        {
            TilesCore::inc_claim_triplet(tile); // 放入序列
            bit_claim_triplet.set(pos);         // 比特置位
            pos_claim_triplet.push_back(pos);   // 位置放入序列
        }
        // 删去刻牌(牌)
        // 仅用于补杠
        void dec_claim_triplet(unsigned char tile)
        {
            unsigned short index = TilesCore::dec_claim_triplet(tile);  // 放入序列
            bit_claim_triplet.reset(pos_claim_triplet[index]);          // 比特置空位
            pos_claim_triplet.erase(pos_claim_triplet.begin() + index); // 位置序列中删去
        }
        // 新增明杠(牌)
        void inc_claim_exposed_kong(unsigned char tile, unsigned short pos)
        {
            TilesCore::inc_claim_exposed_kong(tile); // 放入序列
            bit_claim_exposed_kong.set(pos);         // 比特置位
            pos_claim_exposed_kong.push_back(pos);   // 位置放入序列
        }
        // 字符串按字符分割并变成牌名(bitset格式)
        static unsigned short split_to_bit(string &str, bitset<Tile::kTileCount + 4> &bit_x, vector<unsigned char> &x, vector<unsigned short> &pos_x, unsigned char tile, unsigned short pos)
        {
            if ('1' == str.at(str.size() - 1 - pos))
            {
                bit_x.set(pos);
                x.push_back(tile);
                pos_x.push_back(pos);
                return 1;
            }
            bit_x.reset(pos);
            return 0;
        }
        // 字符串按字符分割并变成牌名(vector格式)
        static void split_to_tile(const string &s, vector<unsigned char> &tokens, unordered_map<unsigned char, unsigned short> &tile_arised, char delim = ' ')
        {
            tokens.clear();
            auto string_find_first_not = [s, delim](size_t pos = 0) -> size_t {
                for (size_t i = pos; i < s.size(); i++)
                {
                    if (s[i] != delim)
                        return i;
                }
                return string::npos;
            };
            size_t lastPos = string_find_first_not(0);
            size_t pos = s.find(delim, lastPos);
            while (lastPos != string::npos)
            {
                unsigned char tile = std::stoi(s.substr(lastPos, pos - lastPos));
                tile_arised[tile] += 1;
                tokens.emplace_back(tile);
                lastPos = string_find_first_not(pos);
                pos = s.find(delim, lastPos);
            }
        }
        // 字符串按字符分割并变成位置(vector格式)
        static void split_to_pos(const string &s, vector<unsigned short> &tokens, bitset<Tile::kTileCount + 4> &bit_set, char delim = ' ')
        {
            tokens.clear();
            auto string_find_first_not = [s, delim](size_t pos = 0) -> size_t {
                for (size_t i = pos; i < s.size(); i++)
                {
                    if (s[i] != delim)
                        return i;
                }
                return string::npos;
            };
            size_t lastPos = string_find_first_not(0);
            size_t pos = s.find(delim, lastPos);
            while (lastPos != string::npos)
            {
                unsigned short index = std::stoi(s.substr(lastPos, pos - lastPos));
                bit_set.set(index);
                tokens.emplace_back(index);
                lastPos = string_find_first_not(pos);
                pos = s.find(delim, lastPos);
            }
        }
    }; // end TilesPlus

    // 自己的牌面(扩展数据集)
    //
    class MyTilesPlus : protected MyTilesCore, public TilesPlus // protected方式继承MyTilesCore(MyTilesCore的函数都要重载),public方式继承TilesPlus
    {
    protected:
        mutable vector<unsigned short> pos_hand;
        mutable vector<unsigned short> pos_claim_concealed_kong;
        mutable bitset<Tile::kTileCount + 4> bit_hand;
        mutable bitset<Tile::kTileCount + 4> bit_claim_concealed_kong;
        friend class MemoryPlus;

    public:
        MyTilesPlus(unsigned short my_player_id) : TilesCore(my_player_id), MyTilesCore(my_player_id), TilesPlus(my_player_id)
        {
            // 为了节省空间,对各牌面数量做一个限制
            pos_hand.reserve(14);                 // 手牌最多14张(包括刚抓上来那张)
            pos_claim_concealed_kong.reserve(16); // 鸣牌中的暗杠最多4副16张
                                                  // PS:手牌+鸣牌最多18张(包括刚抓上来那张)
            Logger::Log("tiles_plus_" + to_string(my_player_id) + "(my)_created");
        }
        ~MyTilesPlus()
        {
            Logger::Log("tiles_plus_" + to_string(TilesPlus::my_player_id_) + "(my)_deleted");
        }
        // 新增手牌(牌)
        void inc_hand(unsigned char tile, unsigned short pos)
        {
            MyTilesCore::inc_hand(tile);
            bit_hand.set(pos);       // 比特置位
            pos_hand.push_back(pos); // 位置放入序列
        }
        // 删去手牌(牌)
        // 注意这里未必是打出,也有可能是鸣牌
        void dec_hand(unsigned char tile)
        {
            unsigned short index = MyTilesCore::dec_hand(tile);
            bit_hand.reset(pos_hand[index]);          // 比特置空位
            pos_hand.erase(pos_hand.begin() + index); // 位置序列中删去
        }
        // 新增暗杠(牌)
        void inc_claim_concealed_kong(unsigned char tile, unsigned short pos)
        {
            MyTilesCore::inc_claim_concealed_kong(tile);
            bit_claim_concealed_kong.set(pos);       // 比特置位
            pos_claim_concealed_kong.push_back(pos); // 位置放入序列
        }
        // 重写:序列化成Json文本,把牌面数据以vector格式存进Data,以便下次重建牌面
        string data_as_vector_to_string() const
        {
            return to_string(TilesCore::wall_count_) + "," + Util::merge_unsigned_char(hand, '_') + "," + Util::merge_unsigned_char(discard, '_') + "," + Util::merge_unsigned_char(claim_sequence, '_') + "," + Util::merge_unsigned_char(claim_triplet, '_') + "," + Util::merge_unsigned_char(claim_exposed_kong, '_') + "," + Util::merge_unsigned_char(claim_concealed_kong, '_') + "," + Util::merge_unsigned_short(pos_hand, '_') + "," + Util::merge_unsigned_short(pos_discard, '_') + "," + Util::merge_unsigned_short(pos_claim_sequence, '_') + "," + Util::merge_unsigned_short(pos_claim_triplet, '_') + "," + Util::merge_unsigned_short(pos_claim_exposed_kong, '_') + "," + Util::merge_unsigned_short(pos_claim_concealed_kong, '_') + ";";
        }
        // 重写:将上回合的data数据(bitset格式)反序列化成数据结构
        void string_to_data_as_vector(string &string_rebuild, unordered_map<unsigned char, unsigned short> &tile_arised)
        {
            vector<string> string_data;
            Util::split(string_rebuild, string_data, ',');
            TilesCore::set_wall_count(stoi(string_data[0]));
            bit_hand.reset();
            bit_discard.reset();
            bit_claim_sequence.reset();
            bit_claim_triplet.reset();
            bit_claim_exposed_kong.reset();
            bit_claim_concealed_kong.reset();
            TilesPlus::split_to_tile(string_data[1], hand, tile_arised, '_');
            TilesPlus::split_to_tile(string_data[2], discard, tile_arised, '_');
            TilesPlus::split_to_tile(string_data[3], claim_sequence, tile_arised, '_');
            TilesPlus::split_to_tile(string_data[4], claim_triplet, tile_arised, '_');
            TilesPlus::split_to_tile(string_data[5], claim_exposed_kong, tile_arised, '_');
            TilesPlus::split_to_tile(string_data[6], claim_concealed_kong, tile_arised, '_');
            TilesPlus::split_to_pos(string_data[7], pos_hand, bit_hand, '_');
            TilesPlus::split_to_pos(string_data[8], pos_discard, bit_discard, '_');
            TilesPlus::split_to_pos(string_data[9], pos_claim_sequence, bit_claim_sequence, '_');
            TilesPlus::split_to_pos(string_data[10], pos_claim_triplet, bit_claim_triplet, '_');
            TilesPlus::split_to_pos(string_data[11], pos_claim_exposed_kong, bit_claim_exposed_kong, '_');
            TilesPlus::split_to_pos(string_data[12], pos_claim_concealed_kong, bit_claim_concealed_kong, '_');
        } // string_to_data_as_vector
        // 重写:序列化成Json文本,把牌面数据以bitset格式存进Data,以便下次重建牌面
        string data_as_bitset_to_string() const
        {
            return to_string(TilesCore::wall_count_) + "," + bit_hand.to_string() + "," + bit_discard.to_string() + "," + bit_claim_sequence.to_string() + "," + bit_claim_triplet.to_string() + "," + bit_claim_exposed_kong.to_string() + "," + bit_claim_concealed_kong.to_string() + ";";
        }
        // 重写:将上回合的data数据(vector格式)反序列化成数据结构
        void string_to_data_as_bitset(string &string_rebuild, vector<unsigned char> &tile_arised)
        {
            vector<string> string_data;
            Util::split(string_rebuild, string_data, ',');
            TilesCore::set_wall_count(stoi(string_data[0]));
            hand.clear();
            discard.clear();
            claim_sequence.clear();
            claim_triplet.clear();
            claim_exposed_kong.clear();
            claim_concealed_kong.clear();
            pos_hand.clear();
            pos_discard.clear();
            pos_claim_sequence.clear();
            pos_claim_triplet.clear();
            pos_claim_exposed_kong.clear();
            pos_claim_concealed_kong.clear();
            for (unsigned short tile = 1; tile <= Tile::kTileNameCount; ++tile)
            {
                unsigned short tile_arised_count = 0;
                for (unsigned short index : {0, 1, 2, 3})
                {
                    unsigned short pos = 4 * bot_tile.kTileValue2Pos[tile] + index;
                    tile_arised_count += TilesPlus::split_to_bit(string_data[1], bit_hand, hand, pos_hand, tile, pos);
                    tile_arised_count += TilesPlus::split_to_bit(string_data[2], bit_discard, discard, pos_discard, tile, pos);
                    tile_arised_count += TilesPlus::split_to_bit(string_data[3], bit_claim_sequence, claim_sequence, pos_claim_sequence, tile, pos);
                    tile_arised_count += TilesPlus::split_to_bit(string_data[4], bit_claim_triplet, claim_triplet, pos_claim_triplet, tile, pos);
                    tile_arised_count += TilesPlus::split_to_bit(string_data[5], bit_claim_exposed_kong, claim_exposed_kong, pos_claim_exposed_kong, tile, pos);
                    tile_arised_count += TilesPlus::split_to_bit(string_data[6], bit_claim_concealed_kong, claim_concealed_kong, pos_claim_exposed_kong, tile, pos);
                } // end for index : {0, 1, 2, 3}
                tile_arised[tile] += tile_arised_count;
            } // end for tile : 1 ~ kTileNameCount
        }     // string_to_data_as_bitset
    };        // end MyTilesPlus

    // 其它玩家牌面
    // 包括:牌墙(数)/弃牌/鸣牌/手牌(数)
    // 其中鸣牌又包括:顺牌/刻牌/明杠/暗杠牌(数)
    class NotMyTilesPlus : protected NotMyTilesCore, public TilesPlus // protected方式继承NotMyTilesCore(NotMyTilesCore的函数都要重载),public方式继承TilesPlus
    {
    protected:
        friend class MemoryPlus;

    public:
        NotMyTilesPlus(unsigned short my_player_id)
            : TilesCore(my_player_id),
              NotMyTilesCore(my_player_id),
              TilesPlus(my_player_id)
        {
            Logger::Log("tiles_plus_" + to_string(my_player_id) + "_created");
        }
        ~NotMyTilesPlus()
        {
            Logger::Log("tiles_plus_" + to_string(TilesPlus::my_player_id_) + "_deleted");
        }
        // 重写:序列化成Json文本,把牌面数据以vector格式存进Data,以便下次重建牌面
        string data_as_vector_to_string() const
        {
            return to_string(TilesCore::wall_count_) + "," + to_string(hand_count_) + "," + to_string(claim_concealed_kong_count_) + "," + Util::merge_unsigned_char(discard, '_') + "," + Util::merge_unsigned_char(claim_sequence, '_') + "," + Util::merge_unsigned_char(claim_triplet, '_') + "," + Util::merge_unsigned_char(claim_exposed_kong, '_') + "," + Util::merge_unsigned_short(pos_discard, '_') + "," + Util::merge_unsigned_short(pos_claim_sequence, '_') + "," + Util::merge_unsigned_short(pos_claim_triplet, '_') + "," + Util::merge_unsigned_short(pos_claim_exposed_kong, '_') + ";";
        }
        // 重写:将上回合的data数据(vector格式)反序列化成数据结构
        void string_to_data_as_vector(string &string_rebuild, unordered_map<unsigned char, unsigned short> &tile_arised)
        {
            vector<string> string_data;
            Util::split(string_rebuild, string_data, ',');
            set_wall_count(stoi(string_data[0]));
            NotMyTilesCore::set_hand_count(stoi(string_data[1]));
            NotMyTilesCore::set_claim_concealed_kong_count(stoi(string_data[2]));
            bit_discard.reset();
            bit_claim_sequence.reset();
            bit_claim_triplet.reset();
            bit_claim_exposed_kong.reset();
            TilesPlus::split_to_tile(string_data[3], discard, tile_arised, '_');
            TilesPlus::split_to_tile(string_data[4], claim_sequence, tile_arised, '_');
            TilesPlus::split_to_tile(string_data[5], claim_triplet, tile_arised, '_');
            TilesPlus::split_to_tile(string_data[6], claim_exposed_kong, tile_arised, '_');
            TilesPlus::split_to_pos(string_data[7], pos_discard, bit_discard, '_');
            TilesPlus::split_to_pos(string_data[8], pos_claim_sequence, bit_claim_sequence, '_');
            TilesPlus::split_to_pos(string_data[9], pos_claim_triplet, bit_claim_triplet, '_');
            TilesPlus::split_to_pos(string_data[10], pos_claim_exposed_kong, bit_claim_exposed_kong, '_');
        } // string_to_data_as_vector
        // 重写:序列化成Json文本,把牌面数据以bitset格式存进Data,以便下次重建牌面
        string data_as_bitset_to_string() const
        {
            return to_string(TilesCore::wall_count_) + "," + to_string(hand_count_) + "," + to_string(claim_concealed_kong_count_) + "," + bit_discard.to_string() + "," + bit_claim_sequence.to_string() + "," + bit_claim_triplet.to_string() + "," + bit_claim_exposed_kong.to_string() + ";";
        }
        // 重写:将上回合的data数据(bitset格式)反序列化成数据结构
        void string_to_data_as_bitset(string &string_rebuild, vector<unsigned char> &tile_arised)
        {
            vector<string> string_data;
            Util::split(string_rebuild, string_data, ',');
            TilesCore::set_wall_count(stoi(string_data[0]));
            NotMyTilesCore::set_hand_count(stoi(string_data[1]));
            NotMyTilesCore::set_claim_concealed_kong_count(stoi(string_data[2]));
            discard.clear();
            claim_sequence.clear();
            claim_triplet.clear();
            claim_exposed_kong.clear();
            pos_discard.clear();
            pos_claim_sequence.clear();
            pos_claim_triplet.clear();
            pos_claim_exposed_kong.clear();
            for (unsigned short tile = 1; tile <= Tile::kTileNameCount; ++tile)
            {
                unsigned short tile_arised_count = 0;
                for (unsigned short index : {0, 1, 2, 3})
                {
                    unsigned short pos = 4 * bot_tile.kTileValue2Pos[tile] + index;
                    tile_arised_count += TilesPlus::split_to_bit(string_data[3], bit_discard, discard, pos_discard, tile, pos);
                    tile_arised_count += TilesPlus::split_to_bit(string_data[4], bit_claim_sequence, claim_sequence, pos_claim_sequence, tile, pos);
                    tile_arised_count += TilesPlus::split_to_bit(string_data[5], bit_claim_triplet, claim_triplet, pos_claim_triplet, tile, pos);
                    tile_arised_count += TilesPlus::split_to_bit(string_data[6], bit_claim_exposed_kong, claim_exposed_kong, pos_claim_exposed_kong, tile, pos);
                } // end for index : {0, 1, 2, 3}
                tile_arised[tile] += tile_arised_count;
            } // end for tile : 1 ~ kTileNameCount
        }     // string_to_data_as_bitset
    };        // end NotMyTilesPlus

    // 机器人的存储(基类)
    class Memory
    {
    protected:
        mutable unsigned short my_player_id_;                              // 自己的位置(0/1/2/3),0号为庄家,其它人逆时针按序坐入下家
        mutable unordered_map<unsigned char, unsigned short> tile_arised_; // 全局牌名出现次数:每一种牌出现过多少张
    public:
        explicit Memory(unsigned short my_player_id) : my_player_id_(my_player_id) {}
        virtual ~Memory() {}
        // 自己摸一张牌
        virtual void IDraw(unsigned char tDRAW) = 0;
        // 其它玩家摸一张牌
        virtual void Draw(unsigned short player_id) = 0;
        // 自己打出一张牌
        virtual void IPlay(unsigned char tPLAY) = 0;
        // 其它玩家打出一张牌
        virtual void Play(unsigned short player_id, unsigned char tPLAY) = 0;
        // 自己碰牌
        virtual void IPeng(unsigned short last_player_id, unsigned char tPENG) = 0;
        // 其它玩家碰牌
        virtual void Peng(unsigned short player_id, unsigned short last_player_id, unsigned char tPENG) = 0;
        // 自己吃牌
        virtual void IChi(unsigned short last_player_id, unsigned char tCHI, unsigned char tSEQUENCE_mid) = 0;
        // 其它玩家吃牌
        virtual void Chi(unsigned short player_id, unsigned short last_player_id, unsigned char tCHI, unsigned char tSEQUENCE_mid) = 0;
        // 自己暗杠
        virtual void IAnGang(unsigned char tGANG) = 0;
        // 其它玩家暗杠
        virtual void AnGang(unsigned short player_id) = 0;
        // 自己明杠
        virtual void IMingGang(unsigned short last_player_id, unsigned char tGANG) = 0;
        // 其它玩家明杠
        virtual void MingGang(unsigned short player_id, unsigned short last_player_id, unsigned char tGANG) = 0;
        // 自己补杠
        virtual void IBuGang(unsigned char tBUGANG) = 0;
        // 其它玩家补杠
        virtual void BuGang(unsigned short player_id, unsigned char tBUGANG) = 0;
    };

    // 机器人的存储(精简数据集,用来进行模拟计算)
    class MemoryCore : virtual public Memory
    {
    protected:
        mutable TilesCore *tiles_[4]; // 四位玩家的牌面,这里要装指针不然过程中会有拷贝构造和多次析构
        MyTilesCore *my_tiles_;       // 为了方便操作,专门从四位玩家的牌面当中引用自己那一份(动态类型)
    public:
        explicit MemoryCore(unsigned short my_player_id)
            : Memory(my_player_id), my_tiles_(0)
        {
            for (unsigned short i : {0, 1, 2, 3})
            {
                if (my_player_id_ == i) // 初始化自己的牌面数据
                {
                    my_tiles_ = new MyTilesCore(i);
                    tiles_[i] = my_tiles_;
                }
                else // 初始化其它玩家的牌面数据
                {
                    tiles_[i] = new NotMyTilesCore(i);
                }
            }
            // 通过拷贝赋值绕过每次插入的时间损耗
            tile_arised_ = bot_tile.kTileArised;
            Logger::Log("memory_core_created");
        }
        ~MemoryCore()
        {
            // 逢new必delete
            for (unsigned short i : {0, 1, 2, 3})
            {
                delete tiles_[i];
            }
            Logger::Log("memory_core_deleted");
        }
        // 自己摸一张牌
        void IDraw(unsigned char tDRAW);
        // 其它玩家摸一张牌
        void Draw(unsigned short player_id);
        // 自己打出一张牌
        void IPlay(unsigned char tPLAY);
        // 其它玩家打出一张牌
        void Play(unsigned short player_id, unsigned char tPLAY);
        // 自己碰牌
        void IPeng(unsigned short last_player_id, unsigned char tPENG);
        // 其它玩家碰牌
        void Peng(unsigned short player_id, unsigned short last_player_id, unsigned char tPENG);
        // 自己吃牌
        void IChi(unsigned short last_player_id, unsigned char tCHI, unsigned char tSEQUENCE_mid);
        // 其它玩家吃牌
        void Chi(unsigned short player_id, unsigned short last_player_id, unsigned char tCHI, unsigned char tSEQUENCE_mid);
        // 自己暗杠
        void IAnGang(unsigned char tGANG);
        // 其它玩家暗杠
        void AnGang(unsigned short player_id);
        // 自己明杠
        void IMingGang(unsigned short last_player_id, unsigned char tGANG);
        // 其它玩家明杠
        void MingGang(unsigned short player_id, unsigned short last_player_id, unsigned char tGANG);
        // 自己补杠
        void IBuGang(unsigned char tBUGANG);
        // 其它玩家补杠
        void BuGang(unsigned short player_id, unsigned char tBUGANG);
    }; // end MemoryCore

    // 机器人的存储(完整数据集,用来存储真实数据)
    class MemoryPlus : virtual public Memory
    {
    protected:
        mutable TilesPlus *tiles_[4]; // 四位玩家的牌面,这里要装指针不然过程中会有拷贝构造和多次析构
        MyTilesPlus *my_tiles_;       // 为了方便操作,专门从四位玩家的牌面当中引用自己那一份(动态类型)
    public:
        explicit MemoryPlus(unsigned short my_player_id)
            : Memory(my_player_id), my_tiles_(0)
        {
            for (unsigned short i : {0, 1, 2, 3})
            {
                if (my_player_id_ == i) // 初始化自己的牌面数据
                {
                    my_tiles_ = new MyTilesPlus(i);
                    tiles_[i] = my_tiles_;
                }
                else // 初始化其它玩家的牌面数据
                {
                    tiles_[i] = new NotMyTilesPlus(i);
                }
            }
            // 通过拷贝赋值绕过每次插入的时间损耗
            tile_arised_ = bot_tile.kTileArised;
            Logger::Log("memory_plus_created");
        }
        ~MemoryPlus()
        {
            // 逢new必delete
            for (unsigned short i : {0, 1, 2, 3})
            {
                delete tiles_[i];
            }
            Logger::Log("memory_plus_deleted");
        }
        // 自己摸一张牌
        void IDraw(unsigned char tDRAW);
        // 其它玩家摸一张牌
        void Draw(unsigned short player_id);
        // 自己打出一张牌
        void IPlay(unsigned char tPLAY);
        // 其它玩家打出一张牌
        void Play(unsigned short player_id, unsigned char tPLAY);
        // 自己碰牌
        void IPeng(unsigned short last_player_id, unsigned char tPENG);
        // 其它玩家碰牌
        void Peng(unsigned short player_id, unsigned short last_player_id, unsigned char tPENG);
        // 自己吃牌
        void IChi(unsigned short last_player_id, unsigned char tCHI, unsigned char tSEQUENCE_mid);
        // 其它玩家吃牌
        void Chi(unsigned short player_id, unsigned short last_player_id, unsigned char tCHI, unsigned char tSEQUENCE_mid);
        // 自己暗杠
        void IAnGang(unsigned char tGANG);
        // 其它玩家暗杠
        void AnGang(unsigned short player_id);
        // 自己明杠
        void IMingGang(unsigned short last_player_id, unsigned char tGANG);
        // 其它玩家明杠
        void MingGang(unsigned short player_id, unsigned short last_player_id, unsigned char tGANG);
        // 自己补杠
        void IBuGang(unsigned char tBUGANG);
        // 其它玩家补杠
        void BuGang(unsigned short player_id, unsigned char tBUGANG);
        // 将主要信息输出为字符串(打日志用)
        string Summary() const;
        // 将4个牌面序列化
        string data_to_string() const;
        // 从字符串重建4个牌面
        void string_to_data(string rebuild_data);
    }; // end MemoryPlus

    // 机器人的大脑:拥有存储并善于思考
    // 如果是线性思考的话,可以不经过大脑,直接在Bot::Think中实现
    class BrainOfBot
    {
    protected:
        MemoryPlus *memory_;                  // 大脑中的存储
        mutable unsigned short my_player_id_; // 自己的位置(0/1/2/3),0号为庄家,其它人逆时针按序坐入下家
        // 以下几个字段是Read环节写入的
        mutable vector<string> requests_;  // 全部输入
        mutable vector<string> responses_; // 全部输出
        mutable Json::Value last_data_;    // 从上回合读入的data字段
        // 以下几个字段是每回合都写入的
        mutable Event last_event_;   // 上回合事件:包括turn/code/player_id/action/tPLAY/tDRAW/
        mutable Event now_event_;    // 本回合事件:包括turn/code/player_id/action/tPLAY/tDRAW
        mutable bool now_must_pass_; // 本回合是否必须响应pass,Judge设定本字段供Think和Response使用

    public:
        explicit BrainOfBot()
            : last_data_(Json::Value(Json::objectValue)),
              now_must_pass_(false)
        {
            Logger::Log("brain_created");
        }
        ~BrainOfBot()
        {
            delete memory_;
            Logger::Log("brain_deleted");
        }
        unsigned short turn() const { return responses_.size(); }                                   // 当前回合数
        inline void set_last_data(const Json::Value &last_data) const { last_data_ = last_data; }   // 写入从上回合读入的data字段
        void add_request(string request) { requests_.push_back(request); }                          // 新增request
        void add_response(string response) { responses_.push_back(response); }                      // 新增response
        string get_request(unsigned short index) const { return requests_[index]; }                 // 获取request
        string get_response(unsigned short index) const { return responses_[index]; }               // 获取response
        inline unsigned short my_player_id() const { return my_player_id_; }                        // 获取my_player_id
        inline void set_now_must_pass(bool now_must_pass) const { now_must_pass_ = now_must_pass; } // 写入now_must_pass
        inline bool now_must_pass() const { return now_must_pass_; }                                // 获取now_must_pass
        // 初始化未发牌的全局牌面
        void Sit(const Event now_event);
        // 初始化发牌后的全局牌面
        void Deal(const Event now_event, unsigned char init_hand_short[]);
        // 自己摸牌(非花牌)
        void IDraw(const Event now_event);
        // 其它玩家摸牌(非花牌)
        void Draw(const Event now_event);
        // 玩家出牌(包括自己)
        void Play(const Event now_event);
        // 玩家碰上回合打出的牌,并另出牌
        void Peng(const Event now_event);
        // 玩家吃上回合打出的牌,并另出牌
        void Chi(const Event now_event);
        // 玩家进行了杠,如果上回合是摸牌,则是暗杠,否则杠上回合打出的牌
        void Gang(const Event now_event);
        // 玩家进行了补杠,补杠的牌是tBUGANG
        void BuGang(const Event now_event);
        // 将主要信息输出为字符串(打日志用)
        string Summary() const;
        // 将全部信息输出为data,帮助下一轮快速Rebuild,而不用从第2回合开始重建
        string Data() const;
        // 读取上回合留下的data字段(回合模式)
        bool RebuildFromLastData();
    }; // end BrainOfBot

    // 以后如果有多种Bot,都要继承这个
    class Bot
    {
    protected:
        EyeOfBot *eye_;     // 组合对象:眼睛(接受外部输入),用指针是因为需要根据宏new不同的子类
        MouthOfBot *mouth_; // 组合对象:嘴(标准输出),用指针是因为需要根据宏new不同的子类
        BrainOfBot *brain_; // 组合对象:大脑(拥有存储并善于思考)
    public:
        Bot() // 让大脑引用一下存储
        {
            brain_ = new BrainOfBot();
#if _JSON_IO //如果是Json交互,为机器人生成Json版本的眼睛和嘴
            eye_ = new EyeOfBotJson(brain_);
            mouth_ = new MouthOfBotJson();
#else //如果是简单交互,为机器人生成Simple版本的眼睛和嘴
            eye_ = new EyeOfBotSimple(brain_);
            mouth_ = new MouthOfBotSimple();
#endif
        }
        virtual ~Bot()
        {
            // 有new必delete
            delete brain_;
            delete eye_;
            delete mouth_;
        }
        virtual void Read() = 0;                // [固定步骤]:读取标准输入
        virtual void Sit() = 0;                 // [固定步骤]:落座,设定好方位信息,生成发牌前的全局牌面数据
        virtual void Deal() = 0;                // [固定步骤]:发牌,生成发牌后的全局牌面数据
        virtual bool RebuildFromLastData() = 0; // [特殊步骤]:重建牌面,从上回合的data字段中重建
        virtual void RebuildFromDeal() = 0;     // [特殊步骤]:重建牌面,从第2回合(从0数起)回溯至当前回合
        virtual void Judge() = 0;               // [固定步骤]:基础判断,分析最新输入,为深度思考准备好操作指导
        virtual void Think() = 0;               // [固定步骤]:深度思考,AI算法在这里
        virtual void Response() = 0;            // [固定步骤]:响应,动作包括PASS/PLAY/PENG/CHI/GANG/BUGANG/HU

    }; // end Bot

    // Bot的第一版实现
    class DessertBot : public Bot
    {
    protected:
    public:
        DessertBot() { Logger::Log("dessert_bot_created"); }
        ~DessertBot() { Logger::Log("dessert_bot_deleted"); }
        // 以下为纯虚函数的子类实现
        void Read();
        void Sit();
        void Deal();
        bool RebuildFromLastData();
        void RebuildFromDeal();
        void Judge();
        void Think();
        void Response();
        // 重建一个回合牌面
        void BuildOneTurn(unsigned short turn);
    }; // end DessertBot

    /*
       类实现部分
       不按声明排序,按阅读需要排序
       重要/复杂/常改的靠下
    */

    // 记录log(计入debug字段)
    void Logger::Debug(const string &log)
    {
        // 构造单条日志
        ostringstream log_tmp;
        // 生成单条日志(自带换行)
        log_tmp << "[" << log << "]" << endl;
        // 写入(由笔的类型决定写入debug还是本地打印)
        pen_->output(log_tmp);
        // 清缓存,省空间
        log_tmp.flush();
    } // Logger::Debug

    // 记录初始化耗时
    void Logger::DebugInit(const microseconds time_interval)
    {
        // 构造单条日志
        ostringstream log_tmp;
        // 生成单条日志(自带换行)
        log_tmp << "[init][" << time_interval.count() << "ms]" << endl;
        // 写入(由笔的类型决定写入debug还是本地打印)
        pen_->output(log_tmp);
        // 清缓存,省空间
        log_tmp.flush();
    } // end Logger::DebugInit

    // 记录跳过的步骤
    void Logger::DebugSkip(const string &log)
    {
        // 构造单条日志
        ostringstream log_tmp;
        // 生成单条日志(自带换行)
        log_tmp << "[" << log << "][-]" << endl;
        // 写入(由笔的类型决定写入debug还是本地打印)
        pen_->output(log_tmp);
        // 清缓存,省空间
        log_tmp.flush();
    } // end Logger::DebugSkip

    // 记录MARK开始
    void Logger::DebugMark()
    {
        // 构造单条日志
        ostringstream log_tmp;
        // 生成单条日志(自带换行)
        log_tmp << ">>[MARK]" << endl;
        // 写入(由笔的类型决定写入debug还是本地打印)
        pen_->output(log_tmp);
        // 清缓存,省空间
        log_tmp.flush();
    } // Logger::DebugMarkCost

    // 记录MARK计时
    void Logger::DebugMarkCost(const microseconds time_interval)
    {
        // 构造单条日志
        ostringstream log_tmp;
        // 生成单条日志(自带换行)
        log_tmp << "<<[MARK][" << time_interval.count() << "ms]" << endl;
        // 写入(由笔的类型决定写入debug还是本地打印)
        pen_->output(log_tmp);
        // 清缓存,省空间
        log_tmp.flush();
    } // Logger::DebugMarkCost

    // 记录回合数,log和时间(Bot单步骤专用)
    void Logger::DebugStep(const string &log, const microseconds time_interval)
    {
        // 构造单条日志
        ostringstream log_tmp;
        // 生成单条日志(自带换行)
        log_tmp << "[" << log << "][" << time_interval.count() << "ms]" << endl;
        // 写入(由笔的类型决定写入debug还是本地打印)
        pen_->output(log_tmp);
        // 清缓存,省空间
        log_tmp.flush();
    } // end Logger::DebugStep

    // 设定标记时间为此刻
    void Watch::Mark()
    {
        last_mark_time_ = steady_clock::now();
        bot_logger.DebugMark();
    } // end Watch::Mark

    // 记录上次MARK时间到此刻的耗时
    void Watch::MarkCost()
    {
        bot_logger.DebugMarkCost(TimeFromMark());
    } // Watch::MarkCost

    // 确保json_input有data字段并且是Json::Value类型,而不是string(回合模式)
    Json::Value EyeOfBot::ReadLastDataIfLocal()
    {
#if _READ_DATA_LAST_LOCAL // 如果开关打开,尝试从本地文件读data字段
        Logger::Log("_READ_DATA_LAST_LOCAL");
        ifstream fin_json;
        fin_json.open(bot_config.kFileDataLastLocal, ios::in);
        // 如果打开失败
        if (!fin_json)
        {
            Logger::Log("open_data_last_file_fail");
            return Json::Value(Json::objectValue);
        }
        // 读入data
        Json::Reader json_reader;
        Json::Value json_value;
        if (json_reader.parse(fin_json, json_value))
        {
            // 关闭文件
            fin_json.close();
            return json_value;
        }
        else
        {
            Logger::Log("parse_data_last_json_fail");
            // 关闭文件
            fin_json.close();
            return Json::Value(Json::objectValue);
        }
#else // 如果开关没开,不读本地数据，返回空Json
        return Json::Value(Json::objectValue);
#endif
    }

    // 从输入生成requests和responses和data(JSON交互/回合模式)
    void EyeOfBotJson::ReadRequestAndResponseAndData(istream &in)
    {
        // 临时string,读入数据用
        string str_tmp;
        // 这里等待标准输入
        getline(in, str_tmp);
        // 读入Json
        Json::Value json_input;
        json_reader_.parse(str_tmp, json_input);
        // 获取当前回合数
        // 数据字段中,turn从0开始
        // 注意:官网对战案例中"当前回合:x"之下显示的都是x-1回合的数据,可能是因为当前回合的数据要下回才能拿到,总之是个bug
        // 此刻的turn表示当前等待response的回合数
        unsigned short turn_total = json_input["responses"].size();
        // 从input_json循环读取requests和responses填充到memory
        for (unsigned short turn = 0; turn < turn_total; ++turn)
        {
            // 绕过大脑思考,直接写入存储
            brain_->add_request(json_input["requests"][turn].asString());
            brain_->add_response(json_input["responses"][turn].asString());
        }
        // 读取本轮的request,写入存储
        brain_->add_request(json_input["requests"][turn_total].asString());
        // 如果输入包含data字段
        if (json_input.isMember("data"))
        {
            Json::Reader json_reader;
            Json::Value json_data;
            // 尝试将data字段解析为Json
            if (json_reader.parse(json_input["data"].asString(), json_data))
            {
                // 解析成功写入存储备用(Rebuild环节使用)
                brain_->set_last_data(json_data);
            }
            else
            {
                // 解析失败就当不存在data字段,但也必须写入空值
                brain_->set_last_data(Json::Value(Json::objectValue));
            }
        }
        else
        {
#ifdef _BOTZONE_ONLINE // 如果是线上编译运行,data为空就写入空Json
            brain_->set_last_data(Json::Value(Json::objectValue));
#else // 如果是本地测试,尝试从本地文件读入data字段(是不是真的读取还要看_READ_DATA_LAST_LOCAL宏开关)
            brain_->set_last_data(ReadLastDataIfLocal());
#endif
        }

    } // EyeOfBotJson::ReadRequestAndResponseAndData

    // 读取标准输入(Json交互/回合模式)
    void EyeOfBotJson::ReadStdInput()
    {
        // 以cin为输入流
        ReadRequestAndResponseAndData(cin);
    } // end EyeOfBotJson::ReadStdInput

    // 读取文件输入(Json交互/回合模式)
    void EyeOfBotJson::ReadFileInput()
    {
        // 打开输入文件
        ifstream fin_json;
        fin_json.open(bot_config.kFileRequestJson, ios::in);
        // 如果打开失败
        if (!fin_json)
        {
            Logger::Log("open_request_(json)_fail");
            return;
        }
        // 以fin_json为输入流
        ReadRequestAndResponseAndData(fin_json);
        // 关闭文件
        fin_json.close();
    } // end EyeOfBotJson::ReadFileInput

    // 从输入生成requests和responses和data(JSON交互/回合模式)
    void EyeOfBotSimple::ReadRequestAndResponseAndData(istream &in)
    {
        string str_tmp;
        unsigned short turn_total;
        // 先得到回合数
        in >> turn_total;
        // 第一轮的特殊输入,三个数字,第一位固定是0,第二位是0/1/2/3表示位置,第三是位是风圈
        getline(in, str_tmp);
        // 循环读取requests和responses,写入存储
        // 如果当前是第3回合,那么底下就要遍历从0到2
        for (unsigned short turn = 0; turn < turn_total - 1; ++turn)
        {
            // 绕过大脑思考,直接写入存储
            getline(in, str_tmp);
            brain_->add_request(str_tmp);
            getline(in, str_tmp);
            brain_->add_response(str_tmp);
        }
        // 读取本轮的request,写入存储
        getline(in, str_tmp);
        brain_->add_request(str_tmp);
        // 读取本轮data字段(自己约定是Json格式)
        getline(in, str_tmp);
        // 判断是否读到空行(无data字段)
        if (str_tmp.empty())
        {
#ifdef _BOTZONE_ONLINE // 如果是线上编译运行,data为空就写入空Json
            brain_->set_last_data(Json::Value(Json::objectValue));
#else // 如果是本地测试,尝试从本地文件读入data字段(是不是真的读取还要看_READ_DATA_LAST_LOCAL宏开关)
            brain_->set_last_data(ReadLastDataIfLocal());
#endif
        }
        else
        {
            Json::Reader json_reader;
            Json::Value json_data;
            // 尝试将data字段解析为Json
            if (json_reader.parse(str_tmp, json_data))
                // 解析成功写入存储备用(Rebuild环节使用)
                brain_->set_last_data(json_data);
            else
                // 解析失败就当不存在data字段,但也必须写入空值
                brain_->set_last_data(Json::Value(Json::objectValue));
        }
        // 同样的操作用在globaldata字段
        getline(in, str_tmp);
        // 判断是否读到空行(无globaldata字段)
        if (str_tmp.empty())
        {
        }
        else
        {
        }
    } // end EyeOfBotJson::ReadFileInput

    // 读取标准输入(简单交互/回合模式)
    void EyeOfBotSimple::ReadStdInput()
    {
        // 以cin为输入流
        ReadRequestAndResponseAndData(cin);
    } // end EyeOfBotSimple::ReadStdInput

    // 读取文件输入(简单交互/回合模式)
    void EyeOfBotSimple::ReadFileInput()
    {
        // 打开输入文件
        ifstream fin_simple;
        fin_simple.open(bot_config.kFileRequestSimple, ios::in);
        // 如果打开失败
        if (!fin_simple)
        {
            Logger::Log("open_request_(simple)_fail");
            return;
        }
        // 以fin_json为输入流
        ReadRequestAndResponseAndData(fin_simple);
        // 关闭文件
        fin_simple.close();
    } // end EyeOfBotSimple::ReadFileInput

    // 将标准data字段写入文件(仅测试用)
    void MouthOfBot::WriteDataNowIfLocal(const string &data)
    {
#if _WRITE_DATA_NOW_LOCAL // 如果开关打开,尝试将本回合data字段写入本地文件
        Logger::Log("_WRITE_DATA_NOW_LOCAL");
        // 打开输入文件
        ofstream fout;
        fout.open(bot_config.kFileDataNowLocal, ios::trunc);
        // 如果打开失败
        if (!fout)
        {
            Logger::Log("open_data_now_file_fail");
            return;
        }
        // 写入文件
        fout << data;
        fout.flush();
        // 关闭文件
        fout.close();
#endif
    } // end Mouth::WriteDataNowIfLocal

    // 标准输出(Json交互)
    void MouthOfBotJson::PrintStdOutput(const string &response)
    {
        // 构建响应Json
        Json::Value outputJson;
        outputJson["response"] = response;
        // Json交互标准输出
        cout << outputJson << endl;
    } // end MouthOfBotJson::PrintStdOutput

    // 标准输出(简单交互)
    void MouthOfBotSimple::PrintStdOutput(const string &response)
    {
        cout << response << endl;
    } // end MouthOfBotSimple::PrintStdOutput

    // 自己摸一张牌
    void MemoryCore::IDraw(unsigned char tDRAW)
    {
        my_tiles_->dec_wall_count(1); // 自己牌墙(数)-1
        // 这是该牌名的一张新牌,定位位置
        my_tiles_->inc_hand(tDRAW); // 自己手牌+1
        tile_arised_[tDRAW] += 1;   // 全局牌名出现次数+1

    } // end MemoryCore::IDraw

    // 自己摸一张牌
    void MemoryPlus::IDraw(unsigned char tDRAW)
    {
        my_tiles_->dec_wall_count(1); // 自己牌墙(数)-1
        // 这是该牌名的一张新牌,定位位置
        unsigned short pos = 4 * bot_tile.kTileValue2Pos[tDRAW] + tile_arised_[tDRAW];
        my_tiles_->inc_hand(tDRAW, pos); // 自己手牌+1
        tile_arised_[tDRAW] += 1;        // 全局牌名出现次数+1

    } // end MemoryPlus::IDraw

    // 其它玩家摸一张牌
    void MemoryCore::Draw(unsigned short player_id)
    {
        tiles_[player_id]->dec_wall_count(1); // 玩家牌墙(数)-1
        NotMyTilesCore *not_my_tiles = dynamic_cast<NotMyTilesCore *>(tiles_[player_id]);
        not_my_tiles->inc_hand_count(1); // 玩家手牌(数)+1

    } // end MemoryCore::Draw

    // 其它玩家摸一张牌
    void MemoryPlus::Draw(unsigned short player_id)
    {
        tiles_[player_id]->dec_wall_count(1); // 玩家牌墙(数)-1
        NotMyTilesPlus *not_my_tiles = static_cast<NotMyTilesPlus *>(tiles_[player_id]);
        not_my_tiles->inc_hand_count(1); // 玩家手牌(数)+1

    } // end MemoryPlus::Draw

    // 自己打出一张牌
    void MemoryCore::IPlay(unsigned char tPLAY)
    {
        my_tiles_->inc_discard(tPLAY); // 自己弃牌+1
        my_tiles_->dec_hand(tPLAY);    // 自己手牌-1

    } // end MemoryCore::IPlay

    // 自己打出一张牌
    void MemoryPlus::IPlay(unsigned char tPLAY)
    {
        // 从手牌中找出要打出的那张牌的全局位置
        unsigned short pos = TilesCore::FindLastPos(my_tiles_->hand, my_tiles_->pos_hand, tPLAY);
        my_tiles_->inc_discard(tPLAY, pos); // 自己弃牌+1
        my_tiles_->dec_hand(tPLAY);         // 自己手牌-1

    } // end MemoryPlus::IPlay

    // 其它玩家打出一张牌
    void MemoryCore::Play(unsigned short player_id, unsigned char tPLAY)
    {
        NotMyTilesCore *not_my_tiles = dynamic_cast<NotMyTilesCore *>(tiles_[player_id]);
        tile_arised_[tPLAY] += 1;         // 这张牌的全局牌名出现次数+1
        not_my_tiles->inc_discard(tPLAY); // 玩家的弃牌+1
        not_my_tiles->dec_hand_count(1);  // 玩家的手牌(数)-1

    } // end MemoryCore::Play

    // 其它玩家打出一张牌
    void MemoryPlus::Play(unsigned short player_id, unsigned char tPLAY)
    {
        NotMyTilesPlus *not_my_tiles = static_cast<NotMyTilesPlus *>(tiles_[player_id]);
        // 这是该牌名的一张新牌,定位位置
        unsigned short pos = 4 * bot_tile.kTileValue2Pos[tPLAY] + tile_arised_[tPLAY];
        tile_arised_[tPLAY] += 1;              // 这张牌的全局牌名出现次数+1
        not_my_tiles->inc_discard(tPLAY, pos); // 玩家的弃牌+1
        not_my_tiles->dec_hand_count(1);       // 玩家的手牌(数)-1

    } // end MemoryPlus::Play

    // 自己碰牌
    void MemoryCore::IPeng(unsigned short last_player_id, unsigned char tPENG)
    {
        // 找出上次出牌的玩家的牌面备用
        TilesCore *last_tiles = tiles_[last_player_id];
        my_tiles_->inc_claim_triplet(tPENG); // 自己鸣牌区刻牌+1(刻牌牌名)
        last_tiles->dec_discard(tPENG);      // 上回合出牌者弃牌-1(刻牌牌名)
        // 刻牌需要增减两次牌面
        for (unsigned short i = 0; i <= 1; ++i)
        {
            my_tiles_->inc_claim_triplet(tPENG); // 自己鸣牌区刻牌+1(刻牌牌名)
            my_tiles_->dec_hand(tPENG);          // 手牌-1
        }
    } // end MemoryCore::IPeng

    // 自己碰牌
    void MemoryPlus::IPeng(unsigned short last_player_id, unsigned char tPENG)
    {
        // 找出上次出牌的玩家的牌面备用
        TilesPlus *last_tiles = static_cast<TilesPlus *>(tiles_[last_player_id]);
        // 找出上回合打出的那张牌的全局位置
        unsigned short pos = TilesCore::FindLastPos(last_tiles->discard, last_tiles->pos_discard, tPENG);
        my_tiles_->inc_claim_triplet(tPENG, pos); // 自己鸣牌区刻牌+1(刻牌牌名)
        last_tiles->dec_discard(tPENG);           // 上回合出牌者弃牌-1(刻牌牌名)
        // 刻牌需要增减两次牌面
        for (unsigned short i = 0; i <= 1; ++i)
        {
            // 从自己手牌中找出要碰的那两张牌的全局位置
            pos = TilesCore::FindFirstPos(my_tiles_->hand, my_tiles_->pos_hand, tPENG);
            my_tiles_->inc_claim_triplet(tPENG, pos); // 自己鸣牌区刻牌+1(刻牌牌名)
            my_tiles_->dec_hand(tPENG);               // 手牌-1
        }
    } // end MemoryPlus::IPeng

    // 其它玩家碰牌
    void MemoryCore::Peng(unsigned short player_id, unsigned short last_player_id, unsigned char tPENG)
    {
        NotMyTilesCore *not_my_tiles = dynamic_cast<NotMyTilesCore *>(tiles_[player_id]);
        not_my_tiles->inc_claim_triplet(tPENG);     // 玩家鸣牌区刻牌+1(刻牌牌名)
        tiles_[last_player_id]->dec_discard(tPENG); // 上回合出牌者弃牌-1(刻牌牌名)
        // 刻牌需要增减两次牌面
        for (unsigned short i = 0; i <= 1; ++i)
        {
            tile_arised_[tPENG] += 1;            // 全局牌名出现次数+1
            my_tiles_->inc_claim_triplet(tPENG); // 玩家鸣牌区刻牌+1(刻牌牌名)
            not_my_tiles->dec_hand_count(1);     // 玩家手牌(数)-1
        }
    } // end MemoryCore::Peng

    // 其它玩家碰牌
    void MemoryPlus::Peng(unsigned short player_id, unsigned short last_player_id, unsigned char tPENG)
    {
        NotMyTilesPlus *not_my_tiles = static_cast<NotMyTilesPlus *>(tiles_[player_id]);
        // 找出上次出牌的玩家的牌面备用
        TilesPlus *last_tiles = static_cast<TilesPlus *>(tiles_[last_player_id]);
        // 找出上回合打出的那张牌的全局位置
        unsigned short pos = TilesCore::FindLastPos(last_tiles->discard, last_tiles->pos_discard, tPENG);
        not_my_tiles->inc_claim_triplet(tPENG, pos); // 玩家鸣牌区刻牌+1(刻牌牌名)
        tiles_[last_player_id]->dec_discard(tPENG);  // 上回合出牌者弃牌-1(刻牌牌名)
        // 刻牌需要增减两次牌面
        for (unsigned short i = 0; i <= 1; ++i)
        {
            // 这是该牌名的一张新牌,定位位置
            pos = 4 * bot_tile.kTileValue2Pos[tPENG] + tile_arised_[tPENG];
            tile_arised_[tPENG] += 1;                 // 全局牌名出现次数+1
            my_tiles_->inc_claim_triplet(tPENG, pos); // 玩家鸣牌区刻牌+1(刻牌牌名)
            not_my_tiles->dec_hand_count(1);          // 玩家手牌(数)-1
        }
    } // end MemoryPlus::Peng

    // 自己吃牌
    void MemoryCore::IChi(unsigned short last_player_id, unsigned char tCHI, unsigned char tSEQUENCE_mid)
    {
        // 找出上次出牌的玩家的牌面备用
        TilesCore *last_tiles = tiles_[last_player_id];
        my_tiles_->inc_claim_sequence(tCHI); // 自己鸣牌区顺牌+1(顺牌牌名)
        last_tiles->dec_discard(tCHI);       // 上回合出牌者弃牌-1(顺牌牌名)
        for (short i : {-1, 0, 1})           // 吃牌需要增减两次牌面
        {
            unsigned char tile = tSEQUENCE_mid + i;
            if (tile != tCHI) // 找出属于自己的那两张牌
            {
                my_tiles_->inc_claim_sequence(tile); // 自己鸣牌区顺牌+1(顺牌牌名)
                my_tiles_->dec_hand(tile);           // 自己手牌-1
            }
        }
    } // end MemoryCore::IChi

    // 自己吃牌
    void MemoryPlus::IChi(unsigned short last_player_id, unsigned char tCHI, unsigned char tSEQUENCE_mid)
    {
        // 找出上次出牌的玩家的牌面备用
        TilesPlus *last_tiles = static_cast<TilesPlus *>(tiles_[last_player_id]);
        // 找出上回合打出的那张牌的全局位置
        unsigned short pos = TilesCore::FindLastPos(last_tiles->discard, last_tiles->pos_discard, tCHI);
        my_tiles_->inc_claim_sequence(tCHI, pos); // 自己鸣牌区顺牌+1(顺牌牌名)
        last_tiles->dec_discard(tCHI);            // 上回合出牌者弃牌-1(顺牌牌名)
        for (short i : {-1, 0, 1})                // 吃牌需要增减两次牌面
        {
            unsigned char tile = tSEQUENCE_mid + i;
            if (tile != tCHI) // 找出属于自己的那两张牌
            {
                // 从自己手牌中找出与tCHI组成顺牌的其它两张牌的全局位置
                pos = TilesCore::FindFirstPos(my_tiles_->hand, my_tiles_->pos_hand, tile);
                my_tiles_->inc_claim_sequence(tile, pos); // 自己鸣牌区顺牌+1(顺牌牌名)
                my_tiles_->dec_hand(tile);                // 自己手牌-1
            }
        }
    } // end MemoryPlus::IChi

    // 其它玩家吃牌
    void MemoryCore::Chi(unsigned short player_id, unsigned short last_player_id, unsigned char tCHI, unsigned char tSEQUENCE_mid)
    {
        NotMyTilesCore *not_my_tiles = dynamic_cast<NotMyTilesCore *>(tiles_[player_id]);
        // 找出上次出牌的玩家的牌面备用
        TilesCore *last_tiles = tiles_[last_player_id];
        not_my_tiles->inc_claim_sequence(tCHI); // 玩家鸣牌区顺牌+1(顺牌牌名)
        last_tiles->dec_discard(tCHI);          // 上回合出牌者弃牌-1(顺牌牌名)
        for (short i : {-1, 0, 1})              // 吃牌需要增减两次牌面
        {
            unsigned char tile = tSEQUENCE_mid + i;
            if (tile != tCHI) // 找出属于自己的那两张牌
            {
                tile_arised_[tile] += 1;                // 全局牌名出现次数+1
                not_my_tiles->inc_claim_sequence(tile); // 玩家鸣牌区顺牌+1(顺牌牌名)
                not_my_tiles->dec_hand_count(1);        // 玩家手牌(数)-1
            }
        }
    } // end MemoryCore::Chi

    // 其它玩家吃牌
    void MemoryPlus::Chi(unsigned short player_id, unsigned short last_player_id, unsigned char tCHI, unsigned char tSEQUENCE_mid)
    {
        NotMyTilesPlus *not_my_tiles = static_cast<NotMyTilesPlus *>(tiles_[player_id]);
        // 找出上次出牌的玩家的牌面备用
        TilesPlus *last_tiles = static_cast<TilesPlus *>(tiles_[last_player_id]);
        // 找出上回合打出的那张牌的全局位置
        unsigned short pos = TilesPlus::FindLastPos(last_tiles->discard, last_tiles->pos_discard, tCHI);
        not_my_tiles->inc_claim_sequence(tCHI, pos); // 玩家鸣牌区顺牌+1(顺牌牌名)
        last_tiles->dec_discard(tCHI);               // 上回合出牌者弃牌-1(顺牌牌名)
        for (short i : {-1, 0, 1})                   // 吃牌需要增减两次牌面
        {
            unsigned char tile = tSEQUENCE_mid + i;
            if (tile != tCHI) // 找出属于自己的那两张牌
            {
                // 这是该牌名的一张新牌,定位位置
                pos = 4 * bot_tile.kTileValue2Pos[tile] + tile_arised_[tile];
                tile_arised_[tile] += 1;                     // 全局牌名出现次数+1
                not_my_tiles->inc_claim_sequence(tile, pos); // 玩家鸣牌区顺牌+1(顺牌牌名)
                not_my_tiles->dec_hand_count(1);             // 玩家手牌(数)-1
            }
        }
    } // end MemoryPlus::Chi

    // 自己暗杠
    void MemoryCore::IAnGang(unsigned char tGANG)
    {
        // 暗杠需要增减四次牌面
        for (unsigned short i = 0; i <= 3; ++i)
        {
            my_tiles_->inc_claim_concealed_kong(tGANG); // 自己鸣牌区暗杠+1(暗杠牌名)
            my_tiles_->dec_hand(tGANG);                 // 自己手牌-1
        }
    } // end MemoryCore::IAnGang

    // 自己暗杠
    void MemoryPlus::IAnGang(unsigned char tGANG)
    {
        // 暗杠需要增减四次牌面
        for (unsigned short i = 0; i <= 3; ++i)
        {
            // 从自己手牌中找出要杠的那四张牌的全局位置
            unsigned short pos = TilesCore::FindFirstPos(my_tiles_->hand, my_tiles_->pos_hand, tGANG);
            my_tiles_->inc_claim_concealed_kong(tGANG, pos); // 自己鸣牌区暗杠+1(暗杠牌名)
            my_tiles_->dec_hand(tGANG);                      // 自己手牌-1
        }
    } // end MemoryPlus::IAnGang

    // 自己明杠
    void MemoryCore::IMingGang(unsigned short last_player_id, unsigned char tGANG)
    {
        // 找出上次出牌的玩家的牌面备用
        TilesCore *last_tiles = tiles_[last_player_id];
        my_tiles_->inc_claim_exposed_kong(tGANG); // 自己鸣牌区明杠+1(明杠牌名)
        last_tiles->dec_discard(tGANG);           // 上回合出牌者弃牌-1(明杠牌名)
        // 明杠需要增减三次牌面
        for (unsigned short i = 0; i <= 2; ++i)
        {
            my_tiles_->inc_claim_exposed_kong(tGANG); // 自己鸣牌区明杠+1(明杠牌名)
            my_tiles_->dec_hand(tGANG);               // 自己手牌-1
        }
    } // end MemoryCore::IMingGang

    // 自己明杠
    void MemoryPlus::IMingGang(unsigned short last_player_id, unsigned char tGANG)
    {
        // 找出上次出牌的玩家的牌面备用
        TilesPlus *last_tiles = static_cast<TilesPlus *>(tiles_[last_player_id]);
        // 找出上回合打出的那张牌的全局位置
        unsigned short pos = TilesCore::FindLastPos(last_tiles->discard, last_tiles->pos_discard, tGANG);
        my_tiles_->inc_claim_exposed_kong(tGANG, pos); // 自己鸣牌区明杠+1(明杠牌名)
        last_tiles->dec_discard(tGANG);                // 上回合出牌者弃牌-1(明杠牌名)
        // 明杠需要增减三次牌面
        for (unsigned short i = 0; i <= 2; ++i)
        {
            // 从自己手牌中找出要杠的那三张牌的全局位置
            pos = TilesCore::FindFirstPos(my_tiles_->hand, my_tiles_->pos_hand, tGANG);
            my_tiles_->inc_claim_exposed_kong(tGANG, pos); // 自己鸣牌区明杠+1(明杠牌名)
            my_tiles_->dec_hand(tGANG);                    // 自己手牌-1
        }
    } // end MemoryPlus::IMingGang

    // 其它玩家暗杠
    void MemoryCore::AnGang(unsigned short player_id)
    {
        NotMyTilesCore *not_my_tiles = dynamic_cast<NotMyTilesCore *>(tiles_[player_id]);
        not_my_tiles->dec_hand_count(4);                 // 玩家手牌(数)-4
        not_my_tiles->inc_claim_concealed_kong_count(4); // 玩家鸣牌区暗杠(数)+4

    } // end MemoryCore::AnGang

    // 其它玩家暗杠
    void MemoryPlus::AnGang(unsigned short player_id)
    {
        NotMyTilesPlus *not_my_tiles = static_cast<NotMyTilesPlus *>(tiles_[player_id]);
        not_my_tiles->dec_hand_count(4);                 // 玩家手牌(数)-4
        not_my_tiles->inc_claim_concealed_kong_count(4); // 玩家鸣牌区暗杠(数)+4

    } // end MemoryPlus::AnGang

    // 其它玩家明杠
    void MemoryCore::MingGang(unsigned short player_id, unsigned short last_player_id, unsigned char tGANG)
    {
        NotMyTilesCore *not_my_tiles = dynamic_cast<NotMyTilesCore *>(tiles_[player_id]);
        // 找出上次出牌的玩家的牌面备用
        TilesCore *last_tiles = tiles_[last_player_id];
        not_my_tiles->inc_claim_exposed_kong(tGANG); // 玩家鸣牌区明杠+1(明杠牌名)
        last_tiles->dec_discard(tGANG);              // 上回合出牌者弃牌-1(明杠牌名)
        // 明杠需要增减三次牌面
        for (unsigned short i = 0; i <= 2; ++i)
        {
            tile_arised_[tGANG] += 1;                    // 全局牌名出现次数+1
            not_my_tiles->inc_claim_exposed_kong(tGANG); // 玩家鸣牌区明杠+1(明杠牌名)
            not_my_tiles->dec_hand_count(1);             // 玩家手牌(数)-1
        }
    } // end MemoryCore::MingGang

    // 其它玩家明杠
    void MemoryPlus::MingGang(unsigned short player_id, unsigned short last_player_id, unsigned char tGANG)
    {
        NotMyTilesPlus *not_my_tiles = static_cast<NotMyTilesPlus *>(tiles_[player_id]);
        // 找出上次出牌的玩家的牌面备用
        TilesPlus *last_tiles = static_cast<TilesPlus *>(tiles_[last_player_id]);
        // 找出上回合打出的那张牌的全局位置
        unsigned short pos = TilesCore::FindLastPos(last_tiles->discard, last_tiles->pos_discard, tGANG);
        not_my_tiles->inc_claim_exposed_kong(tGANG, pos); // 玩家鸣牌区明杠+1(明杠牌名)
        last_tiles->dec_discard(tGANG);                   // 上回合出牌者弃牌-1(明杠牌名)
        // 明杠需要增减三次牌面
        for (unsigned short i = 0; i <= 2; ++i)
        {
            // 这是该牌名的一张新牌,定位位置
            pos = 4 * bot_tile.kTileValue2Pos[tGANG] + tile_arised_[tGANG];
            tile_arised_[tGANG] += 1;                         // 全局牌名出现次数+1
            not_my_tiles->inc_claim_exposed_kong(tGANG, pos); // 玩家鸣牌区明杠+1(明杠牌名)
            not_my_tiles->dec_hand_count(1);                  // 玩家手牌(数)-1
        }
    } // end MemoryPlus::MingGang

    // 自己补杠
    void MemoryCore::IBuGang(unsigned char tBUGANG)
    {
        my_tiles_->inc_claim_exposed_kong(tBUGANG); // 自己鸣牌区明杠+1(补杠牌名)
        my_tiles_->dec_hand(tBUGANG);               // 自己牌-1(补杠牌名)
        // 补杠需要增减三次牌面
        for (unsigned short i = 0; i <= 2; ++i)
        {
            my_tiles_->inc_claim_exposed_kong(tBUGANG); // 自己鸣牌区补杠+1(补杠牌名)
            my_tiles_->dec_claim_triplet(tBUGANG);      // 自己鸣牌区刻牌-1
        }
    } // end MemoryCore::IBuGang

    // 自己补杠
    void MemoryPlus::IBuGang(unsigned char tBUGANG)
    {
        // 从手牌中找出要杠的那张牌的全局位置
        unsigned short pos = TilesCore::FindFirstPos(my_tiles_->hand, my_tiles_->pos_hand, tBUGANG);
        my_tiles_->inc_claim_exposed_kong(tBUGANG, pos); // 自己鸣牌区明杠+1(补杠牌名)
        my_tiles_->dec_hand(tBUGANG);                    // 自己牌-1(补杠牌名)
        // 补杠需要增减三次牌面
        for (unsigned short i = 0; i <= 2; ++i)
        {
            // 从自己鸣牌区刻牌中找出要补杠的那三张牌的全局位置
            pos = TilesCore::FindFirstPos(my_tiles_->claim_triplet, my_tiles_->pos_claim_triplet, tBUGANG);
            my_tiles_->inc_claim_exposed_kong(tBUGANG, pos); // 自己鸣牌区补杠+1(补杠牌名)
            my_tiles_->dec_claim_triplet(tBUGANG);           // 自己鸣牌区刻牌-1
        }
    } // end MemoryPlus::IBuGang

    // 其它玩家补杠
    void MemoryCore::BuGang(unsigned short player_id, unsigned char tBUGANG)
    {
        NotMyTilesCore *not_my_tiles = dynamic_cast<NotMyTilesCore *>(tiles_[player_id]);
        tile_arised_[tBUGANG] += 1;                    // 全局牌名出现次数+1(理论上已经到4了)
        not_my_tiles->inc_claim_exposed_kong(tBUGANG); // 玩家鸣牌区明杠+1(补杠牌名)
        not_my_tiles->dec_hand_count(1);               // 玩家手牌(数)-1
        // 补杠需要增减三次牌面
        for (unsigned short i = 0; i <= 2; ++i)
        {
            my_tiles_->inc_claim_exposed_kong(tBUGANG); // 自己鸣牌区补杠+1(补杠牌名)
            my_tiles_->dec_claim_triplet(tBUGANG);      // 自己鸣牌区刻牌-1
        }
    } // end MemoryCore::BuGang

    // 其它玩家补杠
    void MemoryPlus::BuGang(unsigned short player_id, unsigned char tBUGANG)
    {
        NotMyTilesPlus *not_my_tiles = static_cast<NotMyTilesPlus *>(tiles_[player_id]);
        // 这是该牌名的一张新牌,定位位置
        unsigned short pos = 4 * bot_tile.kTileValue2Pos[tBUGANG] + tile_arised_[tBUGANG];
        tile_arised_[tBUGANG] += 1;                         // 全局牌名出现次数+1(理论上已经到4了)
        not_my_tiles->inc_claim_exposed_kong(tBUGANG, pos); // 玩家鸣牌区明杠+1(补杠牌名)
        not_my_tiles->dec_hand_count(1);                    // 玩家手牌(数)-1
        // 补杠需要增减三次牌面
        for (unsigned short i = 0; i <= 2; ++i)
        {
            // 从自己鸣牌区刻牌中找出要补杠的那三张牌的全局位置
            pos = TilesCore::FindFirstPos(my_tiles_->claim_triplet, my_tiles_->pos_claim_triplet, tBUGANG);
            my_tiles_->inc_claim_exposed_kong(tBUGANG, pos); // 自己鸣牌区补杠+1(补杠牌名)
            my_tiles_->dec_claim_triplet(tBUGANG);           // 自己鸣牌区刻牌-1
        }
    } // end MemoryPlus::BuGang

    // 将主要信息输出为字符串(打日志用)
    string MemoryPlus::Summary() const
    {
        ostringstream sum_tmp;
        // 分隔符
        sum_tmp.fill('_');
        // 标题栏
        sum_tmp << "tile_w__h__d__s__t_ek_ck" << endl;
        // 右对齐
        sum_tmp.setf(ios::right);
        for (unsigned short i : {0, 1, 2, 3})
        {
            if (my_player_id_ == i)
                sum_tmp << "my:";
            else
                sum_tmp << "t" << i << ":";
            sum_tmp << setw(3) << tiles_[i]->wall_count_;
            if (my_player_id_ == i)
                sum_tmp << setw(3) << static_cast<MyTilesPlus *>(tiles_[i])->hand.size();
            else
                sum_tmp << setw(3) << static_cast<NotMyTilesPlus *>(tiles_[i])->hand_count_;
            sum_tmp << setw(3) << tiles_[i]->discard.size();
            sum_tmp << setw(3) << tiles_[i]->claim_sequence.size();
            sum_tmp << setw(3) << tiles_[i]->claim_triplet.size();
            sum_tmp << setw(3) << tiles_[i]->claim_exposed_kong.size();
            if (my_player_id_ == i)
                sum_tmp << setw(3) << static_cast<MyTilesPlus *>(tiles_[i])->claim_concealed_kong.size();
            else
                sum_tmp << setw(3) << static_cast<NotMyTilesPlus *>(tiles_[i])->claim_concealed_kong_count_;
            sum_tmp << endl;
        }
        return sum_tmp.str();
    } // MemoryPlus::Summary

    // 将4个牌面序列化
    string MemoryPlus::data_to_string() const
    {
        ostringstream oss;
        for (unsigned short i : {0, 1, 2, 3})
        {
            if (my_player_id_ == i)
            {
#if _RESPONSE_WITH_DATA_AS_BIT // 如果以bitset格式输出data
                oss << static_cast<MyTilesPlus *>(tiles_[i])->data_as_bitset_to_string();
#else
                oss << static_cast<MyTilesPlus *>(tiles_[i])->data_as_vector_to_string();
#endif
            }
            else
            {
#if _RESPONSE_WITH_DATA_AS_BIT // 如果以bitset格式输出data
                oss << static_cast<NotMyTilesPlus *>(tiles_[i])->data_as_bitset_to_string();
#else
                oss << static_cast<NotMyTilesPlus *>(tiles_[i])->data_as_vector_to_string();
#endif
            }
        }
        return oss.str();
    } // end MemoryPlus::data_to_string

    // 从字符串重建4个牌面
    void MemoryPlus::string_to_data(string rebuild_data)
    {
        tile_arised_ = bot_tile.kTileArised;
        vector<string> string_data;
        Util::split(rebuild_data, string_data, ';');
        for (unsigned short i : {0, 1, 2, 3})
        {
            if (my_player_id_ == i)
            {
#if _RESPONSE_WITH_DATA_AS_BIT // 如果以bitset格式写入data
                static_cast<MyTilesPlus *>(tiles_[i])->string_to_data_as_bitset(string_data[i], tile_arised_);
#else
                static_cast<MyTilesPlus *>(tiles_[i])->string_to_data_as_vector(string_data[i], tile_arised_);
#endif
            }
            else
            {
#if _RESPONSE_WITH_DATA_AS_BIT // 如果以bitset格式写入data
                static_cast<NotMyTilesPlus *>(tiles_[i])->string_to_data_as_bitset(string_data[i], tile_arised_);
#else
                NotMyTilesPlus *tiles = static_cast<NotMyTilesPlus *>(tiles_[i]);
                tiles->string_to_data_as_vector(string_data[i], tile_arised_);
#endif
            }
        }
    } // end MemoryPlus::string_to_data

    // 初始化未发牌的全局牌面
    void BrainOfBot::Sit(const Event now_event)
    {
        // 更新event(last/now)
        last_event_ = now_event_;
        now_event_ = now_event;
        // 设定自己的位置
        my_player_id_ = now_event.player_id;
        // 主存储采用扩展数据集
        memory_ = new MemoryPlus(now_event.player_id);
    } // end BrainOfBot::Sit

    // 初始化发牌后的全局牌面
    void BrainOfBot::Deal(const Event now_event, unsigned char init_hand_short[])
    {
        // 更新event(last/now)
        last_event_ = now_event_;
        now_event_ = now_event;
        // 本操作涉及的数据:
        for (unsigned short player_id : {0, 1, 2, 3})
        {
            if (my_player_id_ == player_id) // 建立自己的牌面
            {
                for (unsigned short j = 0; j < 13; ++j)
                {
                    memory_->IDraw(init_hand_short[j]);
                }
            }
            else // 建立其它玩家的牌面数据
            {
                for (unsigned short j = 0; j < 13; ++j)
                {
                    memory_->Draw(player_id);
                }
            }
        } // end for(0~4)
    }     // end BrainOfBot::Sit

    // 自己摸牌(非花牌)
    void BrainOfBot::IDraw(const Event now_event)
    {
        // 更新event(last/now)
        last_event_ = now_event_;
        now_event_ = now_event;
        memory_->IDraw(now_event.tDRAW);
    } // end BrainOfBot::IDraw

    // 其它玩家摸牌(非花牌)
    void BrainOfBot::Draw(const Event now_event)
    {
        // 更新event(last/now)
        last_event_ = now_event_;
        now_event_ = now_event;
        memory_->Draw(now_event.player_id);
    } // end BrainOfBot::Draw

    // 玩家出牌(包括自己)
    void BrainOfBot::Play(const Event now_event)
    {
        // 更新event(last/now)
        last_event_ = now_event_;
        now_event_ = now_event;
        // 本操作涉及的数据:
        if (now_event.player_id == my_player_id_) // 如果是自己出牌
        {
            memory_->IPlay(now_event.tPLAY);
        }
        else // 如果是其它玩家出牌
        {
            memory_->Play(now_event.player_id, now_event.tPLAY);
        }
    } // end BrainOfBot::Play

    // 玩家碰上回合打出的牌,并另出牌
    void BrainOfBot::Peng(const Event now_event)
    {
        // 更新event(last/now)
        last_event_ = now_event_;
        now_event_ = now_event;
        if (now_event.player_id == my_player_id_) // 如果是自己碰并出牌
        {
            memory_->IPeng(last_event_.player_id, last_event_.tPLAY);
            memory_->IPlay(now_event.tPLAY);
        }
        else // 如果是其它玩家碰并出牌
        {
            memory_->Peng(now_event.player_id, last_event_.player_id, last_event_.tPLAY);
            memory_->Play(now_event.player_id, now_event.tPLAY);
        }
    } // end BrainOfBot::Chi

    // 玩家吃上回合打出的牌,并另出牌
    void BrainOfBot::Chi(const Event now_event)
    {
        // 更新event(last/now)
        last_event_ = now_event_;
        now_event_ = now_event;
        // 本回合吃的牌,就是上回合出的牌
        if (now_event.player_id == my_player_id_) // 如果是自己吃并出牌
        {
            memory_->IChi(last_event_.player_id, last_event_.tPLAY, now_event.tSEQUENCE_mid);
            memory_->IPlay(now_event.tPLAY);
        }
        else // 如果是其它玩家吃并出牌
        {
            memory_->Chi(now_event.player_id, last_event_.player_id, last_event_.tPLAY, now_event.tSEQUENCE_mid);
            memory_->Play(now_event.player_id, now_event.tPLAY);
        }
    } // end BrainOfBot::Chi

    // 玩家进行了杠,如果上回合是摸牌,则是暗杠,否则杠上回合打出的牌
    void BrainOfBot::Gang(const Event now_event)
    {
        // 更新event(last/now)
        last_event_ = now_event_;
        now_event_ = now_event;
        // 本操作涉及的数据:
        if (now_event.player_id == my_player_id_) // 如果是自己杠
        {
            if (2 == last_event_.code) // 上回合是自己摸牌:本回合是自己暗杠
            {
                // 上回合response解析器
                istringstream sin_response;
                unsigned short action;
                string tGANG_str;
                // 取第turn-1回合(从0数起)的输出信息
                sin_response.str(get_request(now_event.turn - 1));
                // 按约定应该是"GANG XX"格式
                sin_response >> action >> tGANG_str;
                memory_->IAnGang(bot_tile.kTileName2Value.at(tGANG_str));
            }
            else // 上回合是其它玩家出牌:本回合是自己明杠
            {
                memory_->IMingGang(last_event_.player_id, last_event_.tPLAY);
            }
        }
        else // 如果是其它玩家杠
        {
            if (3 == last_event_.code && now_event.player_id == last_event_.player_id && DRAW == last_event_.action) // 上回合是该玩家摸牌:本回合是该玩家暗杠
            {
                memory_->AnGang(now_event.player_id);
            }
            else // 上回合是该玩家之外的玩家(有可能包括自己)出牌:本回合是该玩家明杠
            {
                memory_->MingGang(now_event.player_id, last_event_.player_id, last_event_.tPLAY);
            }
        } // end if player_id != my_player_id_
    }     // end BrainOfBot::Gang

    // 玩家进行了补杠
    void BrainOfBot::BuGang(const Event now_event)
    {
        // 更新event(last/now)
        last_event_ = now_event_;
        now_event_ = now_event;
        // 本操作涉及的数据:
        if (now_event.player_id == my_player_id_) // 如果是自己补杠
        {
            memory_->IBuGang(now_event.tBUGANG);
        }
        else // 如果是其它玩家补杠
        {
            memory_->BuGang(now_event.player_id, now_event.tBUGANG);
        }
    } // end BrainOfBot::BuGang

    // 将全部信息输出为data,帮助下一轮快速Rebuild,而不用从第2回合开始重建
    string BrainOfBot::Data() const
    {
        string rebuild_data = last_event_.data_to_string() + now_event_.data_to_string() + memory_->data_to_string();
        Json::Value json_data;
        json_data["rebuild"] = rebuild_data;
        Json::FastWriter fast_writer;
        return fast_writer.write(json_data);
    } // end BrainOfBot::Data

    // 读取上回合留下的data(回合模式)
    bool BrainOfBot::RebuildFromLastData()
    {
        if (!last_data_.isMember("rebuild"))
        {
            return false;
        }
        string rebuild_data = last_data_["rebuild"].asString();
        vector<string> string_data;
        Util::split(rebuild_data, string_data, '|');
        last_event_.string_to_data(string_data[0]);
        now_event_.string_to_data(string_data[1]);
        memory_->string_to_data(string_data[2]);
        return true;
    } // end BrainOfBot::RebuildFromLastData

    // 将主要信息输出为字符串(打日志用)
    string BrainOfBot::Summary() const
    {
        // 格式:[回合数]/w牌墙/h手牌/d弃牌/s顺子/t刻子/ek明杠/ck暗杠
        // tile_w__h__d__s__t_ek_ck
        // t0:_15_13__2__3__3__4__4
        // my:_15_13__2__3__3__4__4
        // t2:_15_13__2__3__3__4__4
        // t3:_15_13__2__3__3__4__4
        // total_144
        // event__t_c_id_action_tD_tP_tS_tB
        // last:__0_0__2_BUGANG_11_11_11_11
        // now:___1_0__2___PLAY_11_11_11_11
        ostringstream sum_tmp;
        // 先换行
        sum_tmp << endl;
        sum_tmp << memory_->Summary();
        sum_tmp.fill('_');
        sum_tmp << "event__t_c_id_action_tD_tP_tS_tB" << endl;
        sum_tmp << "last:"
                << setw(3) << last_event_.turn
                << setw(2) << last_event_.code
                << setw(3) << last_event_.player_id
                << setw(7) << bot_tile.kActionRMap.at(last_event_.action)
                << setw(3) << bot_tile.kTileValue2Name[last_event_.tDRAW]
                << setw(3) << bot_tile.kTileValue2Name[last_event_.tPLAY]
                << setw(3) << bot_tile.kTileValue2Name[last_event_.tSEQUENCE_mid]
                << setw(3) << bot_tile.kTileValue2Name[last_event_.tBUGANG]
                << endl;
        sum_tmp << "now:_"
                << setw(3) << now_event_.turn
                << setw(2) << now_event_.code
                << setw(3) << now_event_.player_id
                << setw(7) << bot_tile.kActionRMap.at(now_event_.action)
                << setw(3) << bot_tile.kTileValue2Name[now_event_.tDRAW]
                << setw(3) << bot_tile.kTileValue2Name[now_event_.tPLAY]
                << setw(3) << bot_tile.kTileValue2Name[now_event_.tSEQUENCE_mid]
                << setw(3) << bot_tile.kTileValue2Name[now_event_.tBUGANG]
                << endl;
        string sum_str = sum_tmp.str();
        // 清缓存,省空间
        sum_tmp.flush();
        return sum_str;
    } // end BrainOfBot::Summary

    // 继承自Bot:读取标准输入
    // 本步骤输出:回合模式的标准输入
    // 是否对模式(长时/回合)幂等:否
    // TODO(天王有棍):长时运行模式的标准输入
    void DessertBot::Read()
    {
        watch.Step(); // 标记步骤开始
        Logger::Log("read_starting");
        eye_->ReadInput();
        // 记录读取标准输入的耗时
        bot_logger.DebugStep("read", watch.TimeFromStep());
    } // end DesertBot::Read

    // 继承自Bot:落座,设定好方位信息,生成发牌前的全局牌面数据
    // 本步骤输出:风圈/庄家/座位
    // 是否对模式(长时/回合)幂等:是(仅取第0回合)
    void DessertBot::Sit()
    {
        watch.Step(); // 标记步骤开始
        // 生成第1回合(从0数起)事件
        Event now_event = Event_Default;
        now_event.turn = 0;
        now_event.action = SIT;
        // request的第一位,0表示落座信息
        unsigned short code;
        // 自己的位置,0/1/2/3
        unsigned short my_player_id;
        // 风圈,根据文档,这个字段暂时没有作用
        unsigned short quan;
        // 本轮request构建器
        istringstream sin;
        // 取第0回合信息(位置信息)
        sin.str(brain_->get_request(0));
        // 获取事件的:类型/自己的位置/风圈
        sin >> code >> my_player_id >> quan;
        now_event.code = code;
        now_event.player_id = my_player_id;
        brain_->Sit(now_event);
        // 习惯性清标志位
        sin.clear();
        // 记录落座的耗时
        bot_logger.DebugStep("sit:" + to_string(my_player_id), watch.TimeFromStep());
    } // end DessertBot::Sit

    // 继承自Bot:发牌,生成发牌后的全局牌面数据
    // 步骤输出:(发牌局的)全局牌面/玩家牌面
    // 是否对模式(长时/回合)幂等:是(仅取第1回合)
    void DessertBot::Deal()
    {
        if (brain_->turn() == 0) // 如果现在是第0回合(落座)
        {
            // 跳过
            bot_logger.DebugSkip("deal");
        }
        else // 如果现在是第1回合(从0数起)(发牌)以上
        {
            watch.Step(); // 标记步骤开始
            Logger::Log("deal_starting");
            // 生成第1回合(从0数起)事件
            Event now_event = Event_Default;
            now_event.turn = 1;
            now_event.action = DEAL;
            // request的第一位表示事件类型,1表示首轮发牌
            unsigned short code;
            unsigned char init_hua_count[4];
            string init_hand_str[13];
            // 本轮request解析器
            istringstream sin;
            // 取第1回合(从0数起)信息(发牌)
            sin.str(brain_->get_request(1));
            // 第1位是事件类型,值是0/1/2/3,0是位置,1是发牌
            sin >> code;
            now_event.code = code;
            now_event.player_id = brain_->my_player_id();
            // 第2~5位的值是各位玩家摸到的花牌数
            for (unsigned short i : {0, 1, 2, 3})
            {
                sin >> init_hua_count[i];
            }
            // 第6到18位是第1回合(从0数起)收到的13张手牌
            // 初始化一个数字版本
            unsigned char init_hand_short[sizeof(init_hand_str)];
            for (unsigned short i = 0; i < 13; i++)
            {
                sin >> init_hand_str[i];
                // 注意这个用法仅限于c++11以上(下同,不再注解,当前使用c++17)
                init_hand_short[i] = bot_tile.kTileName2Value[init_hand_str[i]];
            }
            /* 这里本来应该有读取花牌,但是新规则已经取消了花牌 */
            // 习惯性清标志位
            sin.clear();
            // 初始化四位玩家的牌面
            brain_->Deal(now_event, init_hand_short);
            // 记录发牌的耗时
            bot_logger.DebugStep("deal", watch.TimeFromStep());
        } // end if turn >=1
    }     // end DessertBot::Deal

    // 继承自Bot:重建牌面,从上回合的data字段中重建
    // 步骤输出:(当前回合的)全局牌面/玩家牌面/自己牌面
    // 是否对模式(长时/回合)幂等:否(长时模式不需要Rebuild)
    // TODO(天王有棍):长时运行模式下跳过此步骤
    bool DessertBot::RebuildFromLastData()
    {
        // 读取一次当前回合数备着下面用
        // 加const是配合"turn_now == xxx"使用的保护机制
        // (当不小心写漏一个等号时会编译报错提醒)
        const unsigned short turn_now = brain_->turn();
        if (turn_now == 0) // 如果现在是第0回合(落座)
        {
            // 不需要重建
            bot_logger.DebugSkip("rebuild_from_last_data");
            return true;
        }
        else if (turn_now == 1) // 如果现在是第1回合(从0数起)(发牌)
        {
            // 不需要重建
            bot_logger.DebugSkip("rebuild_from_last_data");
            return true;
        }
        else // 如果现在是第2回合(从0数起)或以上,那么至少有一个回合的数据需要重建
        {
            watch.Step(); // 标记步骤开始
            Logger::Log("rebuild_from_last_data_starting");
            if (brain_->RebuildFromLastData())
            {
                // 重建当前回合
                BuildOneTurn(turn_now);
                // 记录重建的耗时
                bot_logger.DebugStep("rebuild_from_last_data:" + to_string(turn_now), watch.TimeFromStep());
                return true;
            }
            else
            {
                return false;
            }
        }
    } // end DessertBot::RebuildFromLastData

    // 继承自Bot:重建牌面,从第2回合(从0数起)回溯至当前回合
    // 步骤输出:(当前回合的)全局牌面/玩家牌面/自己牌面
    // 是否对模式(长时/回合)幂等:否(长时模式不需要Rebuild)
    // TODO(天王有棍):长时运行模式下跳过此步骤
    void DessertBot::RebuildFromDeal()
    {
        // 读取一次当前回合数备着下面用
        // 加const是配合"turn_now == xxx"使用的保护机制
        // (当不小心写漏一个等号时会编译报错提醒)
        const unsigned short turn_now = brain_->turn();
        if (turn_now == 0) // 如果现在是第0回合(落座)
        {
            // 不需要重建
            bot_logger.DebugSkip("rebuild_from_deal");
        }
        else if (turn_now == 1) // 如果现在是第1回合(从0数起)(发牌)
        {
            // 不需要重建
            bot_logger.DebugSkip("rebuild_from_deal");
        }
        else // 如果现在是第2回合(从0数起)或以上,那么至少有一个回合的数据需要重建
        {
            watch.Step(); // 标记步骤开始
            Logger::Log("rebuild_from_deal_starting");
            // 重建从第2回合(从0数起)至当前回合的牌面
            // 有可能当前就是第2回合(从0数起),那就仅做一次
            for (unsigned short turn = 2; turn <= turn_now; ++turn)
            {
                BuildOneTurn(turn);
            } // end for(turn_now)
            // 记录重建的耗时
            bot_logger.DebugStep("rebuild_from_deal:" + to_string(turn_now), watch.TimeFromStep());
        } // end if turn_now>=2
    }     // end DessertBot::RebuildFromDeal

    // 重建一个回合牌面
    void DessertBot::BuildOneTurn(unsigned short turn)
    {
        // 本轮request解析器
        istringstream sin_request;
        // 生成第1回合(从0数起)事件
        Event now_event = Event_Default;
        now_event.turn = turn;
        // 第一部分:读取requests[turn]
        // request的第一位表示事件类型
        unsigned short code;
        // 取第turn回合(从0数起)的输入信息
        sin_request.str(brain_->get_request(turn));
        // 第0位是事件类型
        sin_request >> code;
        // 将本回合code写入存储
        now_event.code = code;
        if (2 == code) // 事件类型为2表示自己摸牌(非花牌)
        {
            // 读牌摸到的牌名
            string tDRAW_str;
            sin_request >> tDRAW_str;
            unsigned char tDRAW_short = bot_tile.kTileName2Value.at(tDRAW_str);
            now_event.player_id = brain_->my_player_id();
            now_event.action = IDRAW;
            now_event.tDRAW = tDRAW_short;
            brain_->IDraw(now_event); // 调整全局牌面
        }
        else if (3 == code) // 事件类型为3表示全局消息
        {
            // 全局事件发生在哪位玩家
            unsigned short player_id;
            sin_request >> player_id;
            // 将本回合player_id写入存储(本回合如果是吃碰杠会用到)
            now_event.player_id = player_id;
            // 全局消息中的动作
            string action_str;
            sin_request >> action_str;
            // 动作变成枚举值以加快判断
            unsigned short action_short = bot_tile.kActionMap.at(action_str);
            // 将本回合action写入存储(本回合如果是吃碰杠会用到)
            now_event.action = action_short;
            // 动作变成枚举值以加快判断
            switch (action_short)
            {
            case DRAW: // 其它玩家摸牌(非花牌)
            {
                brain_->Draw(now_event); // 调整全局牌面
            }
            break;
            case PLAY: // 玩家出牌(包括自己)
            {
                string tPLAY_str; // 玩家出的牌
                sin_request >> tPLAY_str;
                now_event.tPLAY = bot_tile.kTileName2Value.at(tPLAY_str); // 牌名转数字
                brain_->Play(now_event);                                  // 调整全局牌面
            }
            break;
            case PENG: // 玩家碰上回合打出的牌,并另出牌
            {
                string tPLAY_str; // 另出的牌
                sin_request >> tPLAY_str;
                now_event.tPLAY = bot_tile.kTileName2Value.at(tPLAY_str); // 牌名转数字
                brain_->Peng(now_event);                                  // 调整全局牌面
            }
            break;
            case CHI: // 玩家吃上回合打出的牌,并另出牌
            {
                string tSEQUENCE_mid_str; // 顺子中的中间牌
                string tPLAY_str;         // 另出的牌
                sin_request >> tSEQUENCE_mid_str >> tPLAY_str;
                now_event.tSEQUENCE_mid = bot_tile.kTileName2Value.at(tSEQUENCE_mid_str); // 牌名转数字
                now_event.tPLAY = bot_tile.kTileName2Value.at(tPLAY_str);                 // 牌名转数字
                brain_->Chi(now_event);                                                   // 调整全局牌面
            }
            break;
            case GANG: // 玩家进行了杠,如果上回合是摸牌,则是暗杠,否则杠上回合打出的牌
            {
                brain_->Gang(now_event); // 调整全局牌面
            }
            break;
            case BUGANG: // 玩家进行了补杠,补杠的牌是tBUGANG
            {
                string tBUGANG_str;
                sin_request >> tBUGANG_str;
                now_event.tBUGANG = bot_tile.kTileName2Value.at(tBUGANG_str); // 牌名转数字
                brain_->BuGang(now_event);                                    // 调整全局牌面
            }
            break;
            default:
                break;
            }
        } // end if 3==code
        // 清标志位准备下一次读取
        sin_request.clear();
        // 重建每一回合打印一次概要
        // Logger::Log(brain_->Summary());
    }

    // 基础判断:继承自Bot,分析最新输入,为深度思考准备好操作指导
    // 本步骤输出有:必须响应(如文档约定PASS)/可选响应(如吃碰杠)
    // 是否对模式(长时/回合)幂等:是(仅取本回合)
    void DessertBot::Judge()
    {
        watch.Step(); // 标记步骤开始
        Logger::Log("judge_starting");
        // 记录概要
        bot_logger.Debug(brain_->Summary());

        /* TODO(天王用叉):
        计算当前牌面+输入有什么必选响应或者可选响应，以及算番
        */

        // 记录基础分析的耗时
        bot_logger.DebugStep("judge", watch.TimeFromStep());
    }

    // 深度思考:继承自Bot,AI算法在这里
    // 前面Judge步骤会准备好"必须响应"(如文档约定PASS)或者"可选响应"(如吃碰杠)
    //   如果有"必须响应",则利用时间思考
    //   如果有"可选响应",则思考+决策
    //   如果都没有,则思考+决策
    // 是否对模式(长时/回合)幂等:是(仅取本回合)
    void DessertBot::Think()
    {
        bot_logger.DebugSkip("think");

        /* TODO(天王用叉):
        结合Judge的输出综合思考+决策
        */

        /*
        watch.Step(); // 标记步骤开始
        Logger::Log("think_starting");
        // 记录深度思考的耗时
        bot_logger.DebugStep("think", watch.TimeFromStep());
        */
    } // end DessertBot::Think

    // 响应:继承自Bot,动作包括PASS/PLAY/GANG/BUGANG/PENG/CHI/HU
    // 是否对模式(长时/回合)幂等:是(仅取本回合)
    void DessertBot::Response()
    {
        watch.Step(); // 标记步骤开始
        Logger::Log("response_starting");

#if _RESPONSE_WITH_DATA // 如果需要输出data字段
        Logger::Log("_RESPONSE_WITH_DATA");
        // 读取序列化data字段
        string data = brain_->Data();
#endif
#ifndef _BOTZONE_ONLINE // 如果是本地测试,尝试输出本回合生成的data字段到文件(是不是真的会输出还要看_WRITE_DATA_NOW_LOCAL宏开关)
        mouth_->WriteDataNowIfLocal(data);
#endif
        if (brain_->now_must_pass())
        {
            string response = "PASS";
            brain_->add_response(response);
            mouth_->PrintStdOutput(response);
        }
        else
        {
            // TODO(天王用叉):输出Think之后的决策
        }
        // 记录响应的耗时
        bot_logger.DebugStep("response", watch.TimeFromStep());
    } // end DessertBot::Response

    /* 两种模式的main */

    // TODO(天王用棍):程序入口1:长时运行模式
    int daemon_based_main()
    {
        return 0;
    } // end daemon_based_main

    // 程序入口2:回合模式
    int turn_based_main()
    {
        // 初始化一个DessertBot,如果以后有别的Bot只要改这里的类型就好了
        DessertBot bot;
        // 记录程序完成初始化的耗时
        bot_logger.DebugInit(watch.TimeFromInit());
        // 读取标准输入
        bot.Read();
        // 标记一下MARK时间,后面获取MarkCost()的时候从这里计时
        // 因为bot的每个步骤都已经有单独的Step计时了,所以MARK机制的作用是自定义测试某几个步骤的耗时
        // 这一行根据debug需要,放在哪里都行,只要跟后面MarkCost()前后成对使用即可
        watch.Mark();
        // 落座
        bot.Sit();
        // 尝试从上回合的data字段中重建牌面
        if (!bot.RebuildFromLastData())
        {
            // 如果不成功(没有正确的data字段),就只好从第1回合(发牌)开始重建牌面
            // 发牌
            bot.Deal();
            // 从第2回合(从0数起)回溯至当前回合重建牌面
            bot.RebuildFromDeal();
        }
        // 记录MARK时间到此刻的耗时
        watch.MarkCost();
        // 基础判断
        bot.Judge();
        // 深度思考
        bot.Think();
        // 响应
        bot.Response();
        // 打印debug字段看看
        //cout << "<<<<<<<<<" << endl;
        //cout << bot_logger.debug() << endl;
        //cout << "<<<<<<<<<" << endl;
        // 结束
        return 0;
    } // end turn_based_main

} // namespace skyking

/* 程序从这里开始执行 */

int main()
{
    // 在这里补一个start
    // 其实真正的start在bot_init_time生成的时刻
    skyking::Logger::Log("main_start");
#if _DAEMON_BASED // 1表示长时运行模式
    return skyking::daemon_based_main();
#else // 0表示回合模式
    return skyking::turn_based_main();
#endif
} // end main